@kubb/adapter-oas 5.0.0-beta.93 → 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,8 +5,8 @@ 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.
@@ -344,7 +344,7 @@ async function read(path) {
344
344
  return readFile(path, { encoding: "utf8" });
345
345
  }
346
346
  //#endregion
347
- //#region src/bundler.ts
347
+ //#region src/factory.ts
348
348
  const urlRegExp = /^https?:\/+/i;
349
349
  async function readSource(sourcePath) {
350
350
  if (urlRegExp.test(sourcePath)) {
@@ -387,8 +387,6 @@ async function bundleDocument(pathOrUrl) {
387
387
  await resolver(pathOrUrl);
388
388
  return await bundle(pathOrUrl, resolver);
389
389
  }
390
- //#endregion
391
- //#region src/factory.ts
392
390
  /**
393
391
  * Loads and bundles an OpenAPI document, returning the raw `Document`.
394
392
  *
@@ -458,19 +456,12 @@ async function validateDocument(document, { throwOnError = false } = {}) {
458
456
  }
459
457
  }
460
458
  //#endregion
461
- //#region src/guards.ts
459
+ //#region src/oas.ts
462
460
  /**
463
461
  * Returns `true` when a schema should be treated as nullable.
464
462
  *
465
463
  * Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
466
464
  * `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
467
- *
468
- * @example
469
- * ```ts
470
- * isNullable({ type: 'string', nullable: true }) // true
471
- * isNullable({ type: ['string', 'null'] }) // true
472
- * isNullable({ type: 'string' }) // false
473
- * ```
474
465
  */
475
466
  function isNullable(schema) {
476
467
  if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
@@ -481,24 +472,13 @@ function isNullable(schema) {
481
472
  }
482
473
  /**
483
474
  * Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
484
- *
485
- * @example
486
- * ```ts
487
- * isReference({ $ref: '#/components/schemas/Pet' }) // true
488
- * isReference({ type: 'string' }) // false
489
- * ```
490
475
  */
491
476
  function isReference(obj) {
492
477
  return !!obj && typeof obj === "object" && "$ref" in obj;
493
478
  }
494
479
  /**
495
- * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
496
- *
497
- * @example
498
- * ```ts
499
- * isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
500
- * isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
501
- * ```
480
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
481
+ * excluding the Swagger 2 string form.
502
482
  */
503
483
  function isDiscriminator(obj) {
504
484
  const record = obj;
@@ -506,18 +486,10 @@ function isDiscriminator(obj) {
506
486
  }
507
487
  /**
508
488
  * Returns `true` when a schema is a binary payload: an octet-stream string body.
509
- *
510
- * @example
511
- * ```ts
512
- * isBinary({ type: 'string', contentMediaType: 'application/octet-stream' }) // true
513
- * isBinary({ type: 'string' }) // false
514
- * ```
515
489
  */
516
490
  function isBinary(schema) {
517
491
  return schema.type === "string" && schema.contentMediaType === "application/octet-stream";
518
492
  }
519
- //#endregion
520
- //#region src/mime.ts
521
493
  /**
522
494
  * MIME type fragments that mark a media type as JSON-like.
523
495
  *
@@ -610,6 +582,23 @@ function dereferenceWithRef(document, schema) {
610
582
  };
611
583
  return schema;
612
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
+ }
613
602
  //#endregion
614
603
  //#region src/operation.ts
615
604
  /**
@@ -641,31 +630,22 @@ function getResponseStatusCodes({ schema }) {
641
630
  function getResponseByStatusCode({ document, operation, statusCode }) {
642
631
  const responses = operation.schema.responses;
643
632
  if (!responses || isReference(responses)) return false;
644
- const response = responses[statusCode];
645
- if (!response) return false;
646
- if (isReference(response)) {
647
- const resolved = resolveRef(document, response.$ref);
648
- responses[statusCode] = resolved;
649
- if (!resolved || isReference(resolved)) return false;
650
- return resolved;
651
- }
652
- return response;
633
+ return derefInPlace({
634
+ document,
635
+ container: responses,
636
+ key: statusCode
637
+ }) ?? false;
653
638
  }
654
639
  /**
655
640
  * Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
656
641
  * `undefined` when the operation has no request body.
657
642
  */
658
643
  function getRequestBodyContent({ document, operation }) {
659
- const { schema } = operation;
660
- let requestBody = schema.requestBody;
661
- if (!requestBody) return;
662
- if (isReference(requestBody)) {
663
- const resolved = resolveRef(document, requestBody.$ref);
664
- schema.requestBody = resolved;
665
- if (!resolved || isReference(resolved)) return;
666
- requestBody = resolved;
667
- }
668
- return requestBody.content;
644
+ return derefInPlace({
645
+ document,
646
+ container: operation.schema,
647
+ key: "requestBody"
648
+ })?.content;
669
649
  }
670
650
  /**
671
651
  * Returns the request body media type. With `mediaType` set, returns that entry or `false`.
@@ -714,14 +694,12 @@ function getOperations(document) {
714
694
  if (!paths) return operations;
715
695
  for (const path of Object.keys(paths)) {
716
696
  if (path.startsWith("x-")) continue;
717
- let pathItem = paths[path];
697
+ const pathItem = derefInPlace({
698
+ document,
699
+ container: paths,
700
+ key: path
701
+ });
718
702
  if (!pathItem) continue;
719
- if (isReference(pathItem)) {
720
- const resolved = resolveRef(document, pathItem.$ref);
721
- paths[path] = resolved;
722
- if (!resolved || isReference(resolved)) continue;
723
- pathItem = resolved;
724
- }
725
703
  const item = pathItem;
726
704
  for (const method of Object.keys(item)) {
727
705
  if (!SUPPORTED_METHODS.has(method)) continue;
@@ -849,6 +827,15 @@ function getResponseBody(responseBody, contentType) {
849
827
  if (!availableContentType) return false;
850
828
  return body.content[availableContentType];
851
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
+ });
838
+ }
852
839
  /**
853
840
  * Returns the response schema for a given operation and HTTP status code.
854
841
  *
@@ -860,14 +847,6 @@ function getResponseBody(responseBody, contentType) {
860
847
  * getResponseSchema(document, operation, '4XX') // {}
861
848
  * ```
862
849
  */
863
- function resolveResponseRefs(document, operation) {
864
- const responses = operation.schema.responses;
865
- if (!responses) return;
866
- for (const key in responses) {
867
- const schema = responses[key];
868
- if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
869
- }
870
- }
871
850
  function getResponseSchema(document, operation, statusCode, options = {}) {
872
851
  resolveResponseRefs(document, operation);
873
852
  const responseBody = getResponseBody(getResponseByStatusCode({
@@ -906,6 +885,16 @@ function getRequestSchema(document, operation, options = {}) {
906
885
  return dereferenceWithRef(document, schema);
907
886
  }
908
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
+ /**
909
898
  * Flattens a keyword-only `allOf` into its parent schema.
910
899
  *
911
900
  * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
@@ -924,16 +913,6 @@ function getRequestSchema(document, operation, options = {}) {
924
913
  * // returned unchanged, contains a $ref
925
914
  * ```
926
915
  */
927
- /**
928
- * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
929
- * structurally significant on its own (see `structuralKeys`).
930
- *
931
- * A fragment with a structural keyword can't be safely merged into a parent schema.
932
- */
933
- function hasStructuralKeywords(fragment) {
934
- for (const key in fragment) if (structuralKeys.has(key)) return true;
935
- return false;
936
- }
937
916
  function flattenSchema(schema) {
938
917
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
939
918
  const allOfFragments = schema.allOf;
@@ -1185,7 +1164,7 @@ function getResponseBodyContentTypes(document, operation, statusCode) {
1185
1164
  return body.content ? Object.keys(body.content) : [];
1186
1165
  }
1187
1166
  //#endregion
1188
- //#region src/parser.ts
1167
+ //#region src/converters.ts
1189
1168
  /**
1190
1169
  * Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
1191
1170
  *
@@ -1209,7 +1188,7 @@ function normalizeArrayEnum(schema) {
1209
1188
  * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
1210
1189
  * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
1211
1190
  */
1212
- function createNullSchema(schema, name, nullable) {
1191
+ function createNullNode(schema, name, nullable) {
1213
1192
  return ast.factory.createSchema({
1214
1193
  type: "null",
1215
1194
  primitive: "null",
@@ -1239,620 +1218,636 @@ function nameEnums(node, options) {
1239
1218
  return named;
1240
1219
  }
1241
1220
  /**
1242
- * Factory function that creates schema and operation converters for a given OpenAPI context.
1221
+ * Converts a `$ref` schema into a `RefSchemaNode`.
1243
1222
  *
1244
- * Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
1245
- * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1246
- * which works because function declarations hoist.
1247
- *
1248
- * @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`.
1227
+ */
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`.
1249
1245
  */
1250
- function createSchemaParser(ctx) {
1251
- const document = ctx.document;
1252
- /**
1253
- * Tracks `$ref` paths that are currently being resolved to prevent infinite
1254
- * recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
1255
- */
1256
- const resolvingRefs = /* @__PURE__ */ new Set();
1257
- /**
1258
- * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1259
- *
1260
- * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1261
- * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1262
- * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1263
- * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1264
- */
1265
- const resolvedRefCache = /* @__PURE__ */ new Map();
1266
- /**
1267
- * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1268
- * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1269
- * goes `false` for a `$ref` that points at a component the spec never declares.
1270
- */
1271
- const refExistence = /* @__PURE__ */ new Map();
1272
- function refExists(refPath) {
1273
- if (!refExistence.has(refPath)) {
1274
- let exists = false;
1275
- try {
1276
- exists = !!resolveRef(document, refPath);
1277
- } catch {
1278
- exists = false;
1279
- }
1280
- refExistence.set(refPath, exists);
1281
- }
1282
- return refExistence.get(refPath) ?? false;
1283
- }
1284
- /**
1285
- * Converts a `$ref` schema into a `RefSchemaNode`.
1286
- *
1287
- * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1288
- * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1289
- * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1290
- * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1291
- */
1292
- function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1293
- let resolvedSchema = null;
1294
- const refPath = schema.$ref;
1295
- if (refPath && !resolvingRefs.has(refPath)) {
1296
- if (!resolvedRefCache.has(refPath)) {
1297
- try {
1298
- const referenced = resolveRef(document, refPath);
1299
- if (referenced) {
1300
- resolvingRefs.add(refPath);
1301
- resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1302
- resolvingRefs.delete(refPath);
1303
- }
1304
- } catch {}
1305
- resolvedRefCache.set(refPath, resolvedSchema);
1306
- }
1307
- resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1308
- }
1309
- if (refPath && document.components && !refExists(refPath)) return ast.factory.createSchema({
1310
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1311
- type: "unknown"
1312
- });
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;
1313
1256
  return ast.factory.createSchema({
1314
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1315
- type: "ref",
1316
- name: extractRefName(schema.$ref),
1317
- ref: schema.$ref,
1318
- 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
1319
1269
  });
1320
1270
  }
1321
- /**
1322
- * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1323
- */
1324
- function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }) {
1325
- if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1326
- const [memberSchema] = schema.allOf;
1327
- const memberNode = parseSchema({
1328
- schema: memberSchema,
1329
- name
1330
- }, rawOptions);
1331
- const { kind: _kind, ...memberNodeProps } = memberNode;
1332
- const mergedNullable = nullable || memberNode.nullable || void 0;
1333
- const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1334
- return ast.factory.createSchema({
1335
- ...memberNodeProps,
1336
- name,
1337
- title: schema.title ?? memberNode.title,
1338
- description: schema.description ?? memberNode.description,
1339
- deprecated: schema.deprecated ?? memberNode.deprecated,
1340
- nullable: mergedNullable,
1341
- readOnly: schema.readOnly ?? memberNode.readOnly,
1342
- writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1343
- default: mergedDefault,
1344
- examples: extractExamples(schema) ?? memberNode.examples,
1345
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1346
- 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
1347
1286
  });
1287
+ return false;
1348
1288
  }
1349
- const filteredDiscriminantValues = [];
1350
- const allOfMembers = schema.allOf.filter((item) => {
1351
- if (!isReference(item) || !name) return true;
1352
- const deref = resolveRef(document, item.$ref);
1353
- if (!deref || !isDiscriminator(deref)) return true;
1354
- const parentUnion = deref.oneOf ?? deref.anyOf;
1355
- if (!parentUnion) return true;
1356
- const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1357
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1358
- const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1359
- if (inOneOf || inMapping) {
1360
- const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1361
- if (discriminatorValue) filteredDiscriminantValues.push({
1362
- propertyName: deref.discriminator.propertyName,
1363
- value: discriminatorValue
1364
- });
1365
- return false;
1366
- }
1367
- return true;
1368
- }).map((s) => parseSchema({
1369
- schema: s,
1370
- name
1371
- }, rawOptions));
1372
- const syntheticStart = allOfMembers.length;
1373
- if (Array.isArray(schema.required) && schema.required.length) {
1374
- const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1375
- const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1376
- if (missingRequired.length) {
1377
- const resolvedMembers = schema.allOf.flatMap((item) => {
1378
- if (!isReference(item)) return [item];
1379
- const deref = resolveRef(document, item.$ref);
1380
- return deref && !isReference(deref) ? [deref] : [];
1381
- });
1382
- for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1383
- allOfMembers.push(parseSchema({
1384
- schema: {
1385
- properties: { [key]: resolved.properties[key] },
1386
- required: [key]
1387
- },
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,
1388
1313
  name
1389
1314
  }, rawOptions));
1390
1315
  break;
1391
1316
  }
1392
1317
  }
1393
1318
  }
1394
- if (schema.properties) {
1395
- const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1396
- allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
1397
- }
1398
- for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1399
- propertyName,
1400
- value
1401
- }));
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;
1402
1341
  return ast.factory.createSchema({
1403
- type: "intersection",
1404
- members: [...mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1405
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1342
+ type: "object",
1343
+ primitive: "object",
1344
+ properties: [discriminatorProperty]
1406
1345
  });
1407
1346
  }
1408
- /**
1409
- * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1410
- */
1411
- function convertUnion({ schema, name, nullable, defaultValue, rawOptions }) {
1412
- function pickDiscriminatorPropertyNode(node, propertyName) {
1413
- const discriminatorProperty = ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1414
- if (!discriminatorProperty) return null;
1415
- return ast.factory.createSchema({
1416
- type: "object",
1417
- primitive: "object",
1418
- 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;
1419
1371
  });
1420
1372
  }
1421
- function resolveRefSilent($ref) {
1422
- if (!$ref.startsWith("#")) return null;
1423
- return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1424
- }
1425
- function implicitDiscriminantValue(member) {
1426
- if (!discriminator || discriminator.mapping || !isReference(member)) return null;
1427
- const value = extractRefName(member.$ref);
1428
- if (!value) return null;
1429
- const variant = resolveRefSilent(member.$ref);
1430
- if (!variant) return null;
1431
- const propertyName = discriminator.propertyName;
1432
- const seen = /* @__PURE__ */ new Set([member.$ref]);
1433
- function constrains(v) {
1434
- const prop = v.properties?.[propertyName];
1435
- const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1436
- if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1437
- const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1438
- if (!composition) return false;
1439
- return composition.some((m) => {
1440
- if (!isReference(m)) return constrains(m);
1441
- if (seen.has(m.$ref)) return false;
1442
- seen.add(m.$ref);
1443
- const r = resolveRefSilent(m.$ref);
1444
- return r ? constrains(r) : false;
1445
- });
1446
- }
1447
- return constrains(variant) ? null : value;
1448
- }
1449
- const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1450
- const strategy = schema.oneOf ? "one" : "any";
1451
- const unionBase = {
1452
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1453
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1454
- strategy
1455
- };
1456
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1457
- const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1458
- const sharedPropertiesNode = schema.properties ? parseSchema({
1459
- schema: memberBaseSchema,
1460
- name
1461
- }, rawOptions) : void 0;
1462
- if (sharedPropertiesNode || discriminator) {
1463
- const members = unionMembers.map((s) => {
1464
- const ref = isReference(s) ? s.$ref : void 0;
1465
- const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1466
- const memberNode = parseSchema({
1467
- schema: s,
1468
- name
1469
- }, rawOptions);
1470
- if (!discriminatorValue || !discriminator) return memberNode;
1471
- const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
1472
- propertyName: discriminator.propertyName,
1473
- values: [discriminatorValue]
1474
- })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1475
- return ast.factory.createSchema({
1476
- type: "intersection",
1477
- members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1478
- propertyName: discriminator.propertyName,
1479
- value: discriminatorValue
1480
- })]
1481
- });
1482
- });
1483
- const unionNode = ast.factory.createSchema({
1484
- type: "union",
1485
- ...unionBase,
1486
- members
1487
- });
1488
- 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;
1489
1401
  return ast.factory.createSchema({
1490
1402
  type: "intersection",
1491
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1492
- members: [unionNode, sharedPropertiesNode]
1403
+ members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1404
+ propertyName: discriminator.propertyName,
1405
+ value: discriminatorValue
1406
+ })]
1493
1407
  });
1494
- }
1408
+ });
1495
1409
  const unionNode = ast.factory.createSchema({
1496
1410
  type: "union",
1497
1411
  ...unionBase,
1498
- members: unionMembers.map((s) => parseSchema({
1499
- schema: s,
1500
- name
1501
- }, rawOptions))
1412
+ members
1502
1413
  });
1503
- return ast.applyMacros(unionNode, [macroSimplifyUnion], { depth: "shallow" });
1504
- }
1505
- /**
1506
- * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1507
- */
1508
- function convertConst({ schema, name, nullable, defaultValue }) {
1509
- const constValue = schema.const;
1510
- if (constValue === null) return createNullSchema(schema, name);
1511
- const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1414
+ if (!sharedPropertiesNode) return unionNode;
1512
1415
  return ast.factory.createSchema({
1513
- type: "enum",
1514
- primitive: constPrimitive,
1515
- enumValues: [constValue],
1516
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1416
+ type: "intersection",
1417
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1418
+ members: [unionNode, sharedPropertiesNode]
1517
1419
  });
1518
1420
  }
1519
- /**
1520
- * Converts a format-annotated schema into a special-type `SchemaNode`.
1521
- * Returns `null` when the format should fall through to string handling (`dateType: false`).
1522
- */
1523
- function convertFormat({ schema, name, nullable, defaultValue, options }) {
1524
- const base = buildSchemaNode(schema, name, nullable, defaultValue);
1525
- if (schema.format === "int64") return ast.factory.createSchema({
1526
- type: options.integerType === "bigint" ? "bigint" : "integer",
1527
- primitive: "integer",
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({
1528
1464
  ...base,
1529
- min: schema.minimum,
1530
- max: schema.maximum,
1531
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1532
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1465
+ primitive: "string",
1466
+ type: "datetime",
1467
+ offset: dateType.offset,
1468
+ local: dateType.local
1533
1469
  });
1534
- if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1535
- const dateType = getDateType(options, schema.format);
1536
- if (!dateType) return null;
1537
- if (dateType.type === "datetime") return ast.factory.createSchema({
1538
- ...base,
1539
- primitive: "string",
1540
- type: "datetime",
1541
- offset: dateType.offset,
1542
- local: dateType.local
1543
- });
1544
- return ast.factory.createSchema({
1545
- ...base,
1546
- primitive: "string",
1547
- type: dateType.type,
1548
- representation: dateType.representation
1549
- });
1550
- }
1551
- const specialType = getSchemaType(schema.format);
1552
- if (!specialType) return null;
1553
- const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1554
- const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
1555
1470
  return ast.factory.createSchema({
1556
1471
  ...base,
1557
- primitive: specialPrimitive,
1558
- type: specialType,
1559
- ...hasLength ? {
1560
- min: schema.minLength,
1561
- max: schema.maxLength
1562
- } : {}
1472
+ primitive: "string",
1473
+ type: dateType.type,
1474
+ representation: dateType.representation
1563
1475
  });
1564
1476
  }
1565
- /**
1566
- * Converts an `enum` schema into an `EnumSchemaNode`.
1567
- */
1568
- function convertEnum({ schema, name, nullable, type, rawOptions }) {
1569
- if (type === "array") return parseSchema({
1570
- schema: normalizeArrayEnum(schema),
1571
- name
1572
- }, rawOptions);
1573
- const nullInEnum = schema.enum.includes(null);
1574
- const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1575
- if (nullInEnum && filteredValues.length === 0) return createNullSchema(schema, name);
1576
- const enumNullable = nullable || nullInEnum || void 0;
1577
- const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1578
- const enumPrimitive = getPrimitiveType(type);
1579
- const enumBase = {
1580
- type: "enum",
1581
- primitive: enumPrimitive,
1582
- ...buildSchemaNode(schema, name, enumNullable, enumDefault)
1583
- };
1584
- const extensionKey = enumExtensionKeys.find((key) => key in schema);
1585
- const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1586
- if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1587
- const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1588
- const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1589
- const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1590
- const uniqueValues = [...new Set(filteredValues)];
1591
- const seenNames = /* @__PURE__ */ new Set();
1592
- return ast.factory.createSchema({
1593
- ...enumBase,
1594
- primitive: enumPrimitiveType,
1595
- namedEnumValues: uniqueValues.map((value, index) => ({
1596
- name: String(rawEnumNames?.[index] ?? value),
1597
- value,
1598
- primitive: enumPrimitiveType,
1599
- description: rawEnumDescriptions?.[index]
1600
- })).filter((entry) => {
1601
- if (seenNames.has(entry.name)) return false;
1602
- seenNames.add(entry.name);
1603
- return true;
1604
- })
1605
- });
1606
- }
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 ? {
1486
+ min: schema.minLength,
1487
+ max: schema.maxLength
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();
1607
1518
  return ast.factory.createSchema({
1608
1519
  ...enumBase,
1609
- enumValues: [...new Set(filteredValues)]
1610
- });
1611
- }
1612
- /**
1613
- * Converts an object-like schema into an `ObjectSchemaNode`.
1614
- */
1615
- function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }) {
1616
- const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1617
- const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1618
- const resolvedPropSchema = propSchema;
1619
- const propNullable = isNullable(resolvedPropSchema);
1620
- const schemaNode = nameEnums(parseSchema({
1621
- schema: resolvedPropSchema,
1622
- name: childName(name, propName)
1623
- }, rawOptions), {
1624
- parentName: name,
1625
- propName,
1626
- enumSuffix: options.enumSuffix
1627
- });
1628
- return ast.factory.createProperty({
1629
- name: propName,
1630
- schema: {
1631
- ...schemaNode,
1632
- nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1633
- },
1634
- required
1635
- });
1636
- }) : [];
1637
- const additionalProperties = schema.additionalProperties;
1638
- const additionalPropertiesNode = (() => {
1639
- if (additionalProperties === true) return true;
1640
- if (additionalProperties === false) return false;
1641
- if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1642
- if (additionalProperties) return ast.factory.createSchema({ type: options.unknownType });
1643
- })();
1644
- const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1645
- 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;
1646
- const objectNode = ast.factory.createSchema({
1647
- type: "object",
1648
- primitive: "object",
1649
- properties,
1650
- additionalProperties: additionalPropertiesNode,
1651
- patternProperties,
1652
- minProperties: schema.minProperties,
1653
- maxProperties: schema.maxProperties,
1654
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1655
- });
1656
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1657
- const discPropName = schema.discriminator.propertyName;
1658
- const values = Object.keys(schema.discriminator.mapping);
1659
- const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
1660
- return ast.applyMacros(objectNode, [macroDiscriminatorEnum({
1661
- propertyName: discPropName,
1662
- values,
1663
- enumName
1664
- })], { depth: "shallow" });
1665
- }
1666
- return objectNode;
1667
- }
1668
- /**
1669
- * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1670
- */
1671
- function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
1672
- const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions));
1673
- const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: "any" }) : parseSchema({ schema: schema.items }, rawOptions);
1674
- return ast.factory.createSchema({
1675
- type: "tuple",
1676
- primitive: "array",
1677
- items: tupleItems,
1678
- rest,
1679
- min: schema.minItems,
1680
- max: schema.maxItems,
1681
- ...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
+ })
1682
1531
  });
1683
1532
  }
1684
- /**
1685
- * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1686
- */
1687
- function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1688
- const rawItems = schema.items;
1689
- const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
1690
- const items = rawItems ? [parseSchema({
1691
- schema: rawItems,
1692
- name: itemName
1693
- }, rawOptions)] : [];
1694
- return ast.factory.createSchema({
1695
- type: "array",
1696
- primitive: "array",
1697
- items,
1698
- min: schema.minItems,
1699
- max: schema.maxItems,
1700
- unique: schema.uniqueItems ?? void 0,
1701
- ...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
1702
1553
  });
1703
- }
1704
- /**
1705
- * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1706
- */
1707
- function convertString({ schema, name, nullable, defaultValue }) {
1708
- return ast.factory.createSchema({
1709
- type: "string",
1710
- primitive: "string",
1711
- min: schema.minLength,
1712
- max: schema.maxLength,
1713
- pattern: schema.pattern,
1714
- ...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
1715
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" });
1716
1591
  }
1717
- /**
1718
- * Converts a `type: 'number'` or `type: 'integer'` schema.
1719
- */
1720
- function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1721
- return ast.factory.createSchema({
1722
- type,
1723
- primitive: type,
1724
- min: schema.minimum,
1725
- max: schema.maximum,
1726
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1727
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1728
- multipleOf: schema.multipleOf,
1729
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1730
- });
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)
1731
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;
1732
1798
  /**
1733
- * 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`).
1734
1801
  */
1735
- function convertBoolean({ schema, name, nullable, defaultValue }) {
1736
- return ast.factory.createSchema({
1737
- type: "boolean",
1738
- primitive: "boolean",
1739
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1740
- });
1741
- }
1802
+ const resolvingRefs = /* @__PURE__ */ new Set();
1742
1803
  /**
1743
- * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1744
- * 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.
1745
1810
  */
1746
- function convertBlob({ schema, name, nullable, defaultValue }) {
1747
- return ast.factory.createSchema({
1748
- type: "blob",
1749
- primitive: "string",
1750
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1751
- });
1752
- }
1811
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1753
1812
  /**
1754
- * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1755
- *
1756
- * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1757
- * 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.
1758
1816
  */
1759
- function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1760
- const types = schema.type;
1761
- const nonNullTypes = types.filter((t) => t !== "null");
1762
- if (nonNullTypes.length <= 1) return null;
1763
- const arrayNullable = types.includes("null") || nullable || void 0;
1764
- return ast.factory.createSchema({
1765
- type: "union",
1766
- members: nonNullTypes.map((t) => parseSchema({
1767
- schema: {
1768
- ...schema,
1769
- type: t
1770
- },
1771
- name
1772
- }, rawOptions)),
1773
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1774
- });
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;
1775
1829
  }
1776
1830
  /**
1777
- * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1778
- * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1779
- * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1780
- * 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).
1781
1834
  */
1782
- const schemaRules = [
1783
- {
1784
- match: ({ schema }) => isReference(schema),
1785
- convert: convertRef
1786
- },
1787
- {
1788
- match: ({ schema }) => !!schema.allOf?.length,
1789
- convert: convertAllOf
1790
- },
1791
- {
1792
- match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1793
- convert: convertUnion
1794
- },
1795
- {
1796
- match: ({ schema }) => "const" in schema && schema.const !== void 0,
1797
- convert: convertConst
1798
- },
1799
- {
1800
- match: ({ schema }) => !!schema.format,
1801
- convert: convertFormat
1802
- },
1803
- {
1804
- match: ({ schema }) => isBinary(schema),
1805
- convert: convertBlob
1806
- },
1807
- {
1808
- match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1809
- convert: convertMultiType
1810
- },
1811
- {
1812
- match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1813
- convert: convertString
1814
- },
1815
- {
1816
- match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1817
- convert: (ctx) => convertNumeric(ctx, "number")
1818
- },
1819
- {
1820
- match: ({ schema }) => !!schema.enum?.length,
1821
- convert: convertEnum
1822
- },
1823
- {
1824
- match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1825
- convert: convertObject
1826
- },
1827
- {
1828
- match: ({ schema }) => "prefixItems" in schema,
1829
- convert: convertTuple
1830
- },
1831
- {
1832
- match: ({ schema, type }) => type === "array" || "items" in schema,
1833
- convert: convertArray
1834
- },
1835
- {
1836
- match: ({ type }) => type === "string",
1837
- convert: convertString
1838
- },
1839
- {
1840
- match: ({ type }) => type === "number",
1841
- convert: (ctx) => convertNumeric(ctx, "number")
1842
- },
1843
- {
1844
- match: ({ type }) => type === "integer",
1845
- convert: (ctx) => convertNumeric(ctx, "integer")
1846
- },
1847
- {
1848
- match: ({ type }) => type === "boolean",
1849
- convert: convertBoolean
1850
- },
1851
- {
1852
- match: ({ type }) => type === "null",
1853
- 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);
1854
1848
  }
1855
- ];
1849
+ return resolvedRefCache.get(refPath) ?? null;
1850
+ }
1856
1851
  /**
1857
1852
  * Converts an OAS `SchemaObject` into a `SchemaNode`.
1858
1853
  *
@@ -1871,18 +1866,22 @@ function createSchemaParser(ctx) {
1871
1866
  name
1872
1867
  }, rawOptions);
1873
1868
  const nullable = isNullable(schema) || void 0;
1874
- const schemaCtx = {
1869
+ const context = {
1875
1870
  schema,
1876
1871
  name,
1877
1872
  nullable,
1878
1873
  defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1879
1874
  type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1880
1875
  rawOptions,
1881
- options
1876
+ options,
1877
+ parse: parseSchema,
1878
+ document,
1879
+ resolveRefNode,
1880
+ refExists
1882
1881
  };
1883
1882
  for (const rule of schemaRules) {
1884
- if (!rule.match(schemaCtx)) continue;
1885
- const node = rule.convert(schemaCtx);
1883
+ if (!rule.match(context)) continue;
1884
+ const node = rule.convert(context);
1886
1885
  if (node) return node;
1887
1886
  }
1888
1887
  const emptyType = options.emptySchemaType;