@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.cjs CHANGED
@@ -28,8 +28,8 @@ node_path = __toESM(node_path, 1);
28
28
  let node_fs_promises = require("node:fs/promises");
29
29
  let _readme_openapi_parser = require("@readme/openapi-parser");
30
30
  let _scalar_openapi_upgrader = require("@scalar/openapi-upgrader");
31
- let yaml = require("yaml");
32
31
  let api_ref_bundler = require("api-ref-bundler");
32
+ let yaml = require("yaml");
33
33
  //#region src/constants.ts
34
34
  /**
35
35
  * Default parser options applied when no explicit options are provided.
@@ -367,7 +367,7 @@ async function read(path) {
367
367
  return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
368
368
  }
369
369
  //#endregion
370
- //#region src/bundler.ts
370
+ //#region src/factory.ts
371
371
  const urlRegExp = /^https?:\/+/i;
372
372
  async function readSource(sourcePath) {
373
373
  if (urlRegExp.test(sourcePath)) {
@@ -410,8 +410,6 @@ async function bundleDocument(pathOrUrl) {
410
410
  await resolver(pathOrUrl);
411
411
  return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver);
412
412
  }
413
- //#endregion
414
- //#region src/factory.ts
415
413
  /**
416
414
  * Loads and bundles an OpenAPI document, returning the raw `Document`.
417
415
  *
@@ -481,19 +479,12 @@ async function validateDocument(document, { throwOnError = false } = {}) {
481
479
  }
482
480
  }
483
481
  //#endregion
484
- //#region src/guards.ts
482
+ //#region src/oas.ts
485
483
  /**
486
484
  * Returns `true` when a schema should be treated as nullable.
487
485
  *
488
486
  * Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
489
487
  * `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
490
- *
491
- * @example
492
- * ```ts
493
- * isNullable({ type: 'string', nullable: true }) // true
494
- * isNullable({ type: ['string', 'null'] }) // true
495
- * isNullable({ type: 'string' }) // false
496
- * ```
497
488
  */
498
489
  function isNullable(schema) {
499
490
  if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
@@ -504,24 +495,13 @@ function isNullable(schema) {
504
495
  }
505
496
  /**
506
497
  * Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
507
- *
508
- * @example
509
- * ```ts
510
- * isReference({ $ref: '#/components/schemas/Pet' }) // true
511
- * isReference({ type: 'string' }) // false
512
- * ```
513
498
  */
514
499
  function isReference(obj) {
515
500
  return !!obj && typeof obj === "object" && "$ref" in obj;
516
501
  }
517
502
  /**
518
- * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
519
- *
520
- * @example
521
- * ```ts
522
- * isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
523
- * isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
524
- * ```
503
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
504
+ * excluding the Swagger 2 string form.
525
505
  */
526
506
  function isDiscriminator(obj) {
527
507
  const record = obj;
@@ -529,18 +509,10 @@ function isDiscriminator(obj) {
529
509
  }
530
510
  /**
531
511
  * Returns `true` when a schema is a binary payload: an octet-stream string body.
532
- *
533
- * @example
534
- * ```ts
535
- * isBinary({ type: 'string', contentMediaType: 'application/octet-stream' }) // true
536
- * isBinary({ type: 'string' }) // false
537
- * ```
538
512
  */
539
513
  function isBinary(schema) {
540
514
  return schema.type === "string" && schema.contentMediaType === "application/octet-stream";
541
515
  }
542
- //#endregion
543
- //#region src/mime.ts
544
516
  /**
545
517
  * MIME type fragments that mark a media type as JSON-like.
546
518
  *
@@ -633,6 +605,23 @@ function dereferenceWithRef(document, schema) {
633
605
  };
634
606
  return schema;
635
607
  }
608
+ /**
609
+ * Resolves a `$ref` slot in place: when `container[key]` holds a `$ref`, replaces it with the
610
+ * resolved value and returns that value. Returns `null` when the slot is empty, cannot be resolved,
611
+ * or is still a `$ref` after resolving. A non-`$ref` value is returned untouched, without writing.
612
+ *
613
+ * @example
614
+ * ```ts
615
+ * derefInPlace<ResponseObject>({ document, container: operation.schema.responses, key: '200' })
616
+ * ```
617
+ */
618
+ function derefInPlace({ document, container, key }) {
619
+ const value = container[key];
620
+ if (!isReference(value)) return value ? value : null;
621
+ const resolved = resolveRef(document, value.$ref);
622
+ container[key] = resolved;
623
+ return resolved && !isReference(resolved) ? resolved : null;
624
+ }
636
625
  //#endregion
637
626
  //#region src/operation.ts
638
627
  /**
@@ -664,31 +653,22 @@ function getResponseStatusCodes({ schema }) {
664
653
  function getResponseByStatusCode({ document, operation, statusCode }) {
665
654
  const responses = operation.schema.responses;
666
655
  if (!responses || isReference(responses)) return false;
667
- const response = responses[statusCode];
668
- if (!response) return false;
669
- if (isReference(response)) {
670
- const resolved = resolveRef(document, response.$ref);
671
- responses[statusCode] = resolved;
672
- if (!resolved || isReference(resolved)) return false;
673
- return resolved;
674
- }
675
- return response;
656
+ return derefInPlace({
657
+ document,
658
+ container: responses,
659
+ key: statusCode
660
+ }) ?? false;
676
661
  }
677
662
  /**
678
663
  * Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
679
664
  * `undefined` when the operation has no request body.
680
665
  */
681
666
  function getRequestBodyContent({ document, operation }) {
682
- const { schema } = operation;
683
- let requestBody = schema.requestBody;
684
- if (!requestBody) return;
685
- if (isReference(requestBody)) {
686
- const resolved = resolveRef(document, requestBody.$ref);
687
- schema.requestBody = resolved;
688
- if (!resolved || isReference(resolved)) return;
689
- requestBody = resolved;
690
- }
691
- return requestBody.content;
667
+ return derefInPlace({
668
+ document,
669
+ container: operation.schema,
670
+ key: "requestBody"
671
+ })?.content;
692
672
  }
693
673
  /**
694
674
  * Returns the request body media type. With `mediaType` set, returns that entry or `false`.
@@ -737,14 +717,12 @@ function getOperations(document) {
737
717
  if (!paths) return operations;
738
718
  for (const path of Object.keys(paths)) {
739
719
  if (path.startsWith("x-")) continue;
740
- let pathItem = paths[path];
720
+ const pathItem = derefInPlace({
721
+ document,
722
+ container: paths,
723
+ key: path
724
+ });
741
725
  if (!pathItem) continue;
742
- if (isReference(pathItem)) {
743
- const resolved = resolveRef(document, pathItem.$ref);
744
- paths[path] = resolved;
745
- if (!resolved || isReference(resolved)) continue;
746
- pathItem = resolved;
747
- }
748
726
  const item = pathItem;
749
727
  for (const method of Object.keys(item)) {
750
728
  if (!SUPPORTED_METHODS.has(method)) continue;
@@ -872,6 +850,15 @@ function getResponseBody(responseBody, contentType) {
872
850
  if (!availableContentType) return false;
873
851
  return body.content[availableContentType];
874
852
  }
853
+ function resolveResponseRefs(document, operation) {
854
+ const responses = operation.schema.responses;
855
+ if (!responses) return;
856
+ for (const key in responses) derefInPlace({
857
+ document,
858
+ container: responses,
859
+ key
860
+ });
861
+ }
875
862
  /**
876
863
  * Returns the response schema for a given operation and HTTP status code.
877
864
  *
@@ -883,14 +870,6 @@ function getResponseBody(responseBody, contentType) {
883
870
  * getResponseSchema(document, operation, '4XX') // {}
884
871
  * ```
885
872
  */
886
- function resolveResponseRefs(document, operation) {
887
- const responses = operation.schema.responses;
888
- if (!responses) return;
889
- for (const key in responses) {
890
- const schema = responses[key];
891
- if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
892
- }
893
- }
894
873
  function getResponseSchema(document, operation, statusCode, options = {}) {
895
874
  resolveResponseRefs(document, operation);
896
875
  const responseBody = getResponseBody(getResponseByStatusCode({
@@ -929,6 +908,16 @@ function getRequestSchema(document, operation, options = {}) {
929
908
  return dereferenceWithRef(document, schema);
930
909
  }
931
910
  /**
911
+ * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
912
+ * structurally significant on its own (see `structuralKeys`).
913
+ *
914
+ * A fragment with a structural keyword can't be safely merged into a parent schema.
915
+ */
916
+ function hasStructuralKeywords(fragment) {
917
+ for (const key in fragment) if (structuralKeys.has(key)) return true;
918
+ return false;
919
+ }
920
+ /**
932
921
  * Flattens a keyword-only `allOf` into its parent schema.
933
922
  *
934
923
  * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
@@ -947,16 +936,6 @@ function getRequestSchema(document, operation, options = {}) {
947
936
  * // returned unchanged, contains a $ref
948
937
  * ```
949
938
  */
950
- /**
951
- * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
952
- * structurally significant on its own (see `structuralKeys`).
953
- *
954
- * A fragment with a structural keyword can't be safely merged into a parent schema.
955
- */
956
- function hasStructuralKeywords(fragment) {
957
- for (const key in fragment) if (structuralKeys.has(key)) return true;
958
- return false;
959
- }
960
939
  function flattenSchema(schema) {
961
940
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
962
941
  const allOfFragments = schema.allOf;
@@ -1208,7 +1187,7 @@ function getResponseBodyContentTypes(document, operation, statusCode) {
1208
1187
  return body.content ? Object.keys(body.content) : [];
1209
1188
  }
1210
1189
  //#endregion
1211
- //#region src/parser.ts
1190
+ //#region src/converters.ts
1212
1191
  /**
1213
1192
  * Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
1214
1193
  *
@@ -1232,7 +1211,7 @@ function normalizeArrayEnum(schema) {
1232
1211
  * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
1233
1212
  * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
1234
1213
  */
1235
- function createNullSchema(schema, name, nullable) {
1214
+ function createNullNode(schema, name, nullable) {
1236
1215
  return _kubb_ast.ast.factory.createSchema({
1237
1216
  type: "null",
1238
1217
  primitive: "null",
@@ -1262,620 +1241,636 @@ function nameEnums(node, options) {
1262
1241
  return named;
1263
1242
  }
1264
1243
  /**
1265
- * Factory function that creates schema and operation converters for a given OpenAPI context.
1244
+ * Converts a `$ref` schema into a `RefSchemaNode`.
1266
1245
  *
1267
- * Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
1268
- * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1269
- * which works because function declarations hoist.
1270
- *
1271
- * @internal
1246
+ * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1247
+ * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1248
+ * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1249
+ * Circular refs are detected in `resolveRefNode` and leave `schema` as `null`.
1250
+ */
1251
+ function convertRef({ schema, name, nullable, defaultValue, rawOptions, document, resolveRefNode, refExists }) {
1252
+ const refPath = schema.$ref;
1253
+ const resolvedSchema = refPath ? resolveRefNode(refPath, rawOptions) : null;
1254
+ if (refPath && document.components && !refExists(refPath)) return _kubb_ast.ast.factory.createSchema({
1255
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1256
+ type: "unknown"
1257
+ });
1258
+ return _kubb_ast.ast.factory.createSchema({
1259
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1260
+ type: "ref",
1261
+ name: (0, _kubb_ast.extractRefName)(schema.$ref),
1262
+ ref: schema.$ref,
1263
+ schema: resolvedSchema
1264
+ });
1265
+ }
1266
+ /**
1267
+ * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1272
1268
  */
1273
- function createSchemaParser(ctx) {
1274
- const document = ctx.document;
1275
- /**
1276
- * Tracks `$ref` paths that are currently being resolved to prevent infinite
1277
- * recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
1278
- */
1279
- const resolvingRefs = /* @__PURE__ */ new Set();
1280
- /**
1281
- * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1282
- *
1283
- * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1284
- * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1285
- * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1286
- * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1287
- */
1288
- const resolvedRefCache = /* @__PURE__ */ new Map();
1289
- /**
1290
- * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1291
- * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1292
- * goes `false` for a `$ref` that points at a component the spec never declares.
1293
- */
1294
- const refExistence = /* @__PURE__ */ new Map();
1295
- function refExists(refPath) {
1296
- if (!refExistence.has(refPath)) {
1297
- let exists = false;
1298
- try {
1299
- exists = !!resolveRef(document, refPath);
1300
- } catch {
1301
- exists = false;
1302
- }
1303
- refExistence.set(refPath, exists);
1304
- }
1305
- return refExistence.get(refPath) ?? false;
1306
- }
1307
- /**
1308
- * Converts a `$ref` schema into a `RefSchemaNode`.
1309
- *
1310
- * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1311
- * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1312
- * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1313
- * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1314
- */
1315
- function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1316
- let resolvedSchema = null;
1317
- const refPath = schema.$ref;
1318
- if (refPath && !resolvingRefs.has(refPath)) {
1319
- if (!resolvedRefCache.has(refPath)) {
1320
- try {
1321
- const referenced = resolveRef(document, refPath);
1322
- if (referenced) {
1323
- resolvingRefs.add(refPath);
1324
- resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1325
- resolvingRefs.delete(refPath);
1326
- }
1327
- } catch {}
1328
- resolvedRefCache.set(refPath, resolvedSchema);
1329
- }
1330
- resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1331
- }
1332
- if (refPath && document.components && !refExists(refPath)) return _kubb_ast.ast.factory.createSchema({
1333
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1334
- type: "unknown"
1335
- });
1269
+ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
1270
+ if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1271
+ const [memberSchema] = schema.allOf;
1272
+ const memberNode = parse({
1273
+ schema: memberSchema,
1274
+ name
1275
+ }, rawOptions);
1276
+ const { kind: _kind, ...memberNodeProps } = memberNode;
1277
+ const mergedNullable = nullable || memberNode.nullable || void 0;
1278
+ const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1336
1279
  return _kubb_ast.ast.factory.createSchema({
1337
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1338
- type: "ref",
1339
- name: (0, _kubb_ast.extractRefName)(schema.$ref),
1340
- ref: schema.$ref,
1341
- schema: resolvedSchema
1280
+ ...memberNodeProps,
1281
+ name,
1282
+ title: schema.title ?? memberNode.title,
1283
+ description: schema.description ?? memberNode.description,
1284
+ deprecated: schema.deprecated ?? memberNode.deprecated,
1285
+ nullable: mergedNullable,
1286
+ readOnly: schema.readOnly ?? memberNode.readOnly,
1287
+ writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1288
+ default: mergedDefault,
1289
+ examples: extractExamples(schema) ?? memberNode.examples,
1290
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1291
+ format: schema.format ?? memberNode.format
1342
1292
  });
1343
1293
  }
1344
- /**
1345
- * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1346
- */
1347
- function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }) {
1348
- if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1349
- const [memberSchema] = schema.allOf;
1350
- const memberNode = parseSchema({
1351
- schema: memberSchema,
1352
- name
1353
- }, rawOptions);
1354
- const { kind: _kind, ...memberNodeProps } = memberNode;
1355
- const mergedNullable = nullable || memberNode.nullable || void 0;
1356
- const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1357
- return _kubb_ast.ast.factory.createSchema({
1358
- ...memberNodeProps,
1359
- name,
1360
- title: schema.title ?? memberNode.title,
1361
- description: schema.description ?? memberNode.description,
1362
- deprecated: schema.deprecated ?? memberNode.deprecated,
1363
- nullable: mergedNullable,
1364
- readOnly: schema.readOnly ?? memberNode.readOnly,
1365
- writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1366
- default: mergedDefault,
1367
- examples: extractExamples(schema) ?? memberNode.examples,
1368
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1369
- format: schema.format ?? memberNode.format
1294
+ const filteredDiscriminantValues = [];
1295
+ const allOfMembers = schema.allOf.filter((item) => {
1296
+ if (!isReference(item) || !name) return true;
1297
+ const deref = resolveRef(document, item.$ref);
1298
+ if (!deref || !isDiscriminator(deref)) return true;
1299
+ const parentUnion = deref.oneOf ?? deref.anyOf;
1300
+ if (!parentUnion) return true;
1301
+ const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1302
+ const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1303
+ const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1304
+ if (inOneOf || inMapping) {
1305
+ const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1306
+ if (discriminatorValue) filteredDiscriminantValues.push({
1307
+ propertyName: deref.discriminator.propertyName,
1308
+ value: discriminatorValue
1370
1309
  });
1310
+ return false;
1371
1311
  }
1372
- const filteredDiscriminantValues = [];
1373
- const allOfMembers = schema.allOf.filter((item) => {
1374
- if (!isReference(item) || !name) return true;
1375
- const deref = resolveRef(document, item.$ref);
1376
- if (!deref || !isDiscriminator(deref)) return true;
1377
- const parentUnion = deref.oneOf ?? deref.anyOf;
1378
- if (!parentUnion) return true;
1379
- const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1380
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1381
- const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1382
- if (inOneOf || inMapping) {
1383
- const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1384
- if (discriminatorValue) filteredDiscriminantValues.push({
1385
- propertyName: deref.discriminator.propertyName,
1386
- value: discriminatorValue
1387
- });
1388
- return false;
1389
- }
1390
- return true;
1391
- }).map((s) => parseSchema({
1392
- schema: s,
1393
- name
1394
- }, rawOptions));
1395
- const syntheticStart = allOfMembers.length;
1396
- if (Array.isArray(schema.required) && schema.required.length) {
1397
- const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1398
- const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1399
- if (missingRequired.length) {
1400
- const resolvedMembers = schema.allOf.flatMap((item) => {
1401
- if (!isReference(item)) return [item];
1402
- const deref = resolveRef(document, item.$ref);
1403
- return deref && !isReference(deref) ? [deref] : [];
1404
- });
1405
- for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1406
- allOfMembers.push(parseSchema({
1407
- schema: {
1408
- properties: { [key]: resolved.properties[key] },
1409
- required: [key]
1410
- },
1312
+ return true;
1313
+ }).map((s) => parse({
1314
+ schema: s,
1315
+ name
1316
+ }, rawOptions));
1317
+ const syntheticStart = allOfMembers.length;
1318
+ if (Array.isArray(schema.required) && schema.required.length) {
1319
+ const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1320
+ const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1321
+ if (missingRequired.length) {
1322
+ const resolvedMembers = schema.allOf.flatMap((item) => {
1323
+ if (!isReference(item)) return [item];
1324
+ const deref = resolveRef(document, item.$ref);
1325
+ return deref && !isReference(deref) ? [deref] : [];
1326
+ });
1327
+ for (const key of missingRequired) for (const resolved of resolvedMembers) {
1328
+ const prop = resolved.properties?.[key];
1329
+ if (prop) {
1330
+ const memberSchema = {
1331
+ properties: { [key]: prop },
1332
+ required: [key]
1333
+ };
1334
+ allOfMembers.push(parse({
1335
+ schema: memberSchema,
1411
1336
  name
1412
1337
  }, rawOptions));
1413
1338
  break;
1414
1339
  }
1415
1340
  }
1416
1341
  }
1417
- if (schema.properties) {
1418
- const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1419
- allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
1420
- }
1421
- for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1422
- propertyName,
1423
- value
1424
- }));
1342
+ }
1343
+ if (schema.properties) {
1344
+ const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1345
+ allOfMembers.push(parse({ schema: schemaWithoutAllOf }, rawOptions));
1346
+ }
1347
+ for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1348
+ propertyName,
1349
+ value
1350
+ }));
1351
+ return _kubb_ast.ast.factory.createSchema({
1352
+ type: "intersection",
1353
+ members: [...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(0, syntheticStart)), ...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(syntheticStart))],
1354
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1355
+ });
1356
+ }
1357
+ /**
1358
+ * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1359
+ */
1360
+ function convertUnion({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
1361
+ function pickDiscriminatorPropertyNode(node, propertyName) {
1362
+ const discriminatorProperty = _kubb_ast.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1363
+ if (!discriminatorProperty) return null;
1425
1364
  return _kubb_ast.ast.factory.createSchema({
1426
- type: "intersection",
1427
- members: [...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(0, syntheticStart)), ...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(syntheticStart))],
1428
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1365
+ type: "object",
1366
+ primitive: "object",
1367
+ properties: [discriminatorProperty]
1429
1368
  });
1430
1369
  }
1431
- /**
1432
- * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1433
- */
1434
- function convertUnion({ schema, name, nullable, defaultValue, rawOptions }) {
1435
- function pickDiscriminatorPropertyNode(node, propertyName) {
1436
- const discriminatorProperty = _kubb_ast.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1437
- if (!discriminatorProperty) return null;
1438
- return _kubb_ast.ast.factory.createSchema({
1439
- type: "object",
1440
- primitive: "object",
1441
- properties: [discriminatorProperty]
1370
+ function resolveRefSilent($ref) {
1371
+ if (!$ref.startsWith("#")) return null;
1372
+ return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1373
+ }
1374
+ function implicitDiscriminantValue(member) {
1375
+ if (!discriminator || discriminator.mapping || !isReference(member)) return null;
1376
+ const value = (0, _kubb_ast.extractRefName)(member.$ref);
1377
+ if (!value) return null;
1378
+ const variant = resolveRefSilent(member.$ref);
1379
+ if (!variant) return null;
1380
+ const propertyName = discriminator.propertyName;
1381
+ const seen = /* @__PURE__ */ new Set([member.$ref]);
1382
+ function constrains(v) {
1383
+ const prop = v.properties?.[propertyName];
1384
+ const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1385
+ if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1386
+ const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1387
+ if (!composition) return false;
1388
+ return composition.some((m) => {
1389
+ if (!isReference(m)) return constrains(m);
1390
+ if (seen.has(m.$ref)) return false;
1391
+ seen.add(m.$ref);
1392
+ const r = resolveRefSilent(m.$ref);
1393
+ return r ? constrains(r) : false;
1442
1394
  });
1443
1395
  }
1444
- function resolveRefSilent($ref) {
1445
- if (!$ref.startsWith("#")) return null;
1446
- return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1447
- }
1448
- function implicitDiscriminantValue(member) {
1449
- if (!discriminator || discriminator.mapping || !isReference(member)) return null;
1450
- const value = (0, _kubb_ast.extractRefName)(member.$ref);
1451
- if (!value) return null;
1452
- const variant = resolveRefSilent(member.$ref);
1453
- if (!variant) return null;
1454
- const propertyName = discriminator.propertyName;
1455
- const seen = /* @__PURE__ */ new Set([member.$ref]);
1456
- function constrains(v) {
1457
- const prop = v.properties?.[propertyName];
1458
- const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1459
- if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1460
- const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1461
- if (!composition) return false;
1462
- return composition.some((m) => {
1463
- if (!isReference(m)) return constrains(m);
1464
- if (seen.has(m.$ref)) return false;
1465
- seen.add(m.$ref);
1466
- const r = resolveRefSilent(m.$ref);
1467
- return r ? constrains(r) : false;
1468
- });
1469
- }
1470
- return constrains(variant) ? null : value;
1471
- }
1472
- const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1473
- const strategy = schema.oneOf ? "one" : "any";
1474
- const unionBase = {
1475
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1476
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1477
- strategy
1478
- };
1479
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1480
- const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1481
- const sharedPropertiesNode = schema.properties ? parseSchema({
1482
- schema: memberBaseSchema,
1483
- name
1484
- }, rawOptions) : void 0;
1485
- if (sharedPropertiesNode || discriminator) {
1486
- const members = unionMembers.map((s) => {
1487
- const ref = isReference(s) ? s.$ref : void 0;
1488
- const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1489
- const memberNode = parseSchema({
1490
- schema: s,
1491
- name
1492
- }, rawOptions);
1493
- if (!discriminatorValue || !discriminator) return memberNode;
1494
- const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_ast.ast.applyMacros(sharedPropertiesNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1495
- propertyName: discriminator.propertyName,
1496
- values: [discriminatorValue]
1497
- })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1498
- return _kubb_ast.ast.factory.createSchema({
1499
- type: "intersection",
1500
- members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1501
- propertyName: discriminator.propertyName,
1502
- value: discriminatorValue
1503
- })]
1504
- });
1505
- });
1506
- const unionNode = _kubb_ast.ast.factory.createSchema({
1507
- type: "union",
1508
- ...unionBase,
1509
- members
1510
- });
1511
- if (!sharedPropertiesNode) return unionNode;
1396
+ return constrains(variant) ? null : value;
1397
+ }
1398
+ const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1399
+ const strategy = schema.oneOf ? "one" : "any";
1400
+ const unionBase = {
1401
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1402
+ discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1403
+ strategy
1404
+ };
1405
+ const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1406
+ const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1407
+ const sharedPropertiesNode = schema.properties ? parse({
1408
+ schema: memberBaseSchema,
1409
+ name
1410
+ }, rawOptions) : void 0;
1411
+ if (sharedPropertiesNode || discriminator) {
1412
+ const members = unionMembers.map((s) => {
1413
+ const ref = isReference(s) ? s.$ref : void 0;
1414
+ const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1415
+ const memberNode = parse({
1416
+ schema: s,
1417
+ name
1418
+ }, rawOptions);
1419
+ if (!discriminatorValue || !discriminator) return memberNode;
1420
+ const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_ast.ast.applyMacros(sharedPropertiesNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1421
+ propertyName: discriminator.propertyName,
1422
+ values: [discriminatorValue]
1423
+ })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1512
1424
  return _kubb_ast.ast.factory.createSchema({
1513
1425
  type: "intersection",
1514
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1515
- members: [unionNode, sharedPropertiesNode]
1426
+ members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1427
+ propertyName: discriminator.propertyName,
1428
+ value: discriminatorValue
1429
+ })]
1516
1430
  });
1517
- }
1431
+ });
1518
1432
  const unionNode = _kubb_ast.ast.factory.createSchema({
1519
1433
  type: "union",
1520
1434
  ...unionBase,
1521
- members: unionMembers.map((s) => parseSchema({
1522
- schema: s,
1523
- name
1524
- }, rawOptions))
1435
+ members
1525
1436
  });
1526
- return _kubb_ast.ast.applyMacros(unionNode, [_kubb_ast.macroSimplifyUnion], { depth: "shallow" });
1527
- }
1528
- /**
1529
- * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1530
- */
1531
- function convertConst({ schema, name, nullable, defaultValue }) {
1532
- const constValue = schema.const;
1533
- if (constValue === null) return createNullSchema(schema, name);
1534
- const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1437
+ if (!sharedPropertiesNode) return unionNode;
1535
1438
  return _kubb_ast.ast.factory.createSchema({
1536
- type: "enum",
1537
- primitive: constPrimitive,
1538
- enumValues: [constValue],
1539
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1439
+ type: "intersection",
1440
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1441
+ members: [unionNode, sharedPropertiesNode]
1540
1442
  });
1541
1443
  }
1542
- /**
1543
- * Converts a format-annotated schema into a special-type `SchemaNode`.
1544
- * Returns `null` when the format should fall through to string handling (`dateType: false`).
1545
- */
1546
- function convertFormat({ schema, name, nullable, defaultValue, options }) {
1547
- const base = buildSchemaNode(schema, name, nullable, defaultValue);
1548
- if (schema.format === "int64") return _kubb_ast.ast.factory.createSchema({
1549
- type: options.integerType === "bigint" ? "bigint" : "integer",
1550
- primitive: "integer",
1444
+ const unionNode = _kubb_ast.ast.factory.createSchema({
1445
+ type: "union",
1446
+ ...unionBase,
1447
+ members: unionMembers.map((s) => parse({
1448
+ schema: s,
1449
+ name
1450
+ }, rawOptions))
1451
+ });
1452
+ return _kubb_ast.ast.applyMacros(unionNode, [_kubb_ast.macroSimplifyUnion], { depth: "shallow" });
1453
+ }
1454
+ /**
1455
+ * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1456
+ */
1457
+ function convertConst({ schema, name, nullable, defaultValue }) {
1458
+ const constValue = schema.const;
1459
+ if (constValue === null) return createNullNode(schema, name);
1460
+ const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1461
+ return _kubb_ast.ast.factory.createSchema({
1462
+ type: "enum",
1463
+ primitive: constPrimitive,
1464
+ enumValues: [constValue],
1465
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1466
+ });
1467
+ }
1468
+ /**
1469
+ * Converts a format-annotated schema into a special-type `SchemaNode`.
1470
+ * Returns `null` when the format should fall through to string handling (`dateType: false`).
1471
+ */
1472
+ function convertFormat({ schema, name, nullable, defaultValue, options }) {
1473
+ const base = buildSchemaNode(schema, name, nullable, defaultValue);
1474
+ if (schema.format === "int64") return _kubb_ast.ast.factory.createSchema({
1475
+ type: options.integerType === "bigint" ? "bigint" : "integer",
1476
+ primitive: "integer",
1477
+ ...base,
1478
+ min: schema.minimum,
1479
+ max: schema.maximum,
1480
+ exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1481
+ exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1482
+ });
1483
+ if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1484
+ const dateType = getDateType(options, schema.format);
1485
+ if (!dateType) return null;
1486
+ if (dateType.type === "datetime") return _kubb_ast.ast.factory.createSchema({
1551
1487
  ...base,
1552
- min: schema.minimum,
1553
- max: schema.maximum,
1554
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1555
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1488
+ primitive: "string",
1489
+ type: "datetime",
1490
+ offset: dateType.offset,
1491
+ local: dateType.local
1556
1492
  });
1557
- if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1558
- const dateType = getDateType(options, schema.format);
1559
- if (!dateType) return null;
1560
- if (dateType.type === "datetime") return _kubb_ast.ast.factory.createSchema({
1561
- ...base,
1562
- primitive: "string",
1563
- type: "datetime",
1564
- offset: dateType.offset,
1565
- local: dateType.local
1566
- });
1567
- return _kubb_ast.ast.factory.createSchema({
1568
- ...base,
1569
- primitive: "string",
1570
- type: dateType.type,
1571
- representation: dateType.representation
1572
- });
1573
- }
1574
- const specialType = getSchemaType(schema.format);
1575
- if (!specialType) return null;
1576
- const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1577
- const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
1578
1493
  return _kubb_ast.ast.factory.createSchema({
1579
1494
  ...base,
1580
- primitive: specialPrimitive,
1581
- type: specialType,
1582
- ...hasLength ? {
1583
- min: schema.minLength,
1584
- max: schema.maxLength
1585
- } : {}
1495
+ primitive: "string",
1496
+ type: dateType.type,
1497
+ representation: dateType.representation
1586
1498
  });
1587
1499
  }
1588
- /**
1589
- * Converts an `enum` schema into an `EnumSchemaNode`.
1590
- */
1591
- function convertEnum({ schema, name, nullable, type, rawOptions }) {
1592
- if (type === "array") return parseSchema({
1593
- schema: normalizeArrayEnum(schema),
1594
- name
1595
- }, rawOptions);
1596
- const nullInEnum = schema.enum.includes(null);
1597
- const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1598
- if (nullInEnum && filteredValues.length === 0) return createNullSchema(schema, name);
1599
- const enumNullable = nullable || nullInEnum || void 0;
1600
- const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1601
- const enumPrimitive = getPrimitiveType(type);
1602
- const enumBase = {
1603
- type: "enum",
1604
- primitive: enumPrimitive,
1605
- ...buildSchemaNode(schema, name, enumNullable, enumDefault)
1606
- };
1607
- const extensionKey = enumExtensionKeys.find((key) => key in schema);
1608
- const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1609
- if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1610
- const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1611
- const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1612
- const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1613
- const uniqueValues = [...new Set(filteredValues)];
1614
- const seenNames = /* @__PURE__ */ new Set();
1615
- return _kubb_ast.ast.factory.createSchema({
1616
- ...enumBase,
1617
- primitive: enumPrimitiveType,
1618
- namedEnumValues: uniqueValues.map((value, index) => ({
1619
- name: String(rawEnumNames?.[index] ?? value),
1620
- value,
1621
- primitive: enumPrimitiveType,
1622
- description: rawEnumDescriptions?.[index]
1623
- })).filter((entry) => {
1624
- if (seenNames.has(entry.name)) return false;
1625
- seenNames.add(entry.name);
1626
- return true;
1627
- })
1628
- });
1629
- }
1500
+ const specialType = getSchemaType(schema.format);
1501
+ if (!specialType) return null;
1502
+ const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1503
+ const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
1504
+ return _kubb_ast.ast.factory.createSchema({
1505
+ ...base,
1506
+ primitive: specialPrimitive,
1507
+ type: specialType,
1508
+ ...hasLength ? {
1509
+ min: schema.minLength,
1510
+ max: schema.maxLength
1511
+ } : {}
1512
+ });
1513
+ }
1514
+ /**
1515
+ * Converts an `enum` schema into an `EnumSchemaNode`.
1516
+ */
1517
+ function convertEnum({ schema, name, nullable, type, rawOptions, parse }) {
1518
+ if (type === "array") return parse({
1519
+ schema: normalizeArrayEnum(schema),
1520
+ name
1521
+ }, rawOptions);
1522
+ const nullInEnum = schema.enum.includes(null);
1523
+ const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1524
+ if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
1525
+ const enumNullable = nullable || nullInEnum || void 0;
1526
+ const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1527
+ const enumPrimitive = getPrimitiveType(type);
1528
+ const enumBase = {
1529
+ type: "enum",
1530
+ primitive: enumPrimitive,
1531
+ ...buildSchemaNode(schema, name, enumNullable, enumDefault)
1532
+ };
1533
+ const extensionKey = enumExtensionKeys.find((key) => key in schema);
1534
+ const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1535
+ if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1536
+ const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1537
+ const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1538
+ const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1539
+ const uniqueValues = [...new Set(filteredValues)];
1540
+ const seenNames = /* @__PURE__ */ new Set();
1630
1541
  return _kubb_ast.ast.factory.createSchema({
1631
1542
  ...enumBase,
1632
- enumValues: [...new Set(filteredValues)]
1633
- });
1634
- }
1635
- /**
1636
- * Converts an object-like schema into an `ObjectSchemaNode`.
1637
- */
1638
- function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }) {
1639
- const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1640
- const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1641
- const resolvedPropSchema = propSchema;
1642
- const propNullable = isNullable(resolvedPropSchema);
1643
- const schemaNode = nameEnums(parseSchema({
1644
- schema: resolvedPropSchema,
1645
- name: (0, _kubb_ast.childName)(name, propName)
1646
- }, rawOptions), {
1647
- parentName: name,
1648
- propName,
1649
- enumSuffix: options.enumSuffix
1650
- });
1651
- return _kubb_ast.ast.factory.createProperty({
1652
- name: propName,
1653
- schema: {
1654
- ...schemaNode,
1655
- nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1656
- },
1657
- required
1658
- });
1659
- }) : [];
1660
- const additionalProperties = schema.additionalProperties;
1661
- const additionalPropertiesNode = (() => {
1662
- if (additionalProperties === true) return true;
1663
- if (additionalProperties === false) return false;
1664
- if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1665
- if (additionalProperties) return _kubb_ast.ast.factory.createSchema({ type: options.unknownType });
1666
- })();
1667
- const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1668
- const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_ast.ast.factory.createSchema({ type: options.unknownType }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1669
- const objectNode = _kubb_ast.ast.factory.createSchema({
1670
- type: "object",
1671
- primitive: "object",
1672
- properties,
1673
- additionalProperties: additionalPropertiesNode,
1674
- patternProperties,
1675
- minProperties: schema.minProperties,
1676
- maxProperties: schema.maxProperties,
1677
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1678
- });
1679
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1680
- const discPropName = schema.discriminator.propertyName;
1681
- const values = Object.keys(schema.discriminator.mapping);
1682
- const enumName = name ? (0, _kubb_ast.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
1683
- return _kubb_ast.ast.applyMacros(objectNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1684
- propertyName: discPropName,
1685
- values,
1686
- enumName
1687
- })], { depth: "shallow" });
1688
- }
1689
- return objectNode;
1690
- }
1691
- /**
1692
- * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1693
- */
1694
- function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
1695
- const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions));
1696
- const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? _kubb_ast.ast.factory.createSchema({ type: "any" }) : parseSchema({ schema: schema.items }, rawOptions);
1697
- return _kubb_ast.ast.factory.createSchema({
1698
- type: "tuple",
1699
- primitive: "array",
1700
- items: tupleItems,
1701
- rest,
1702
- min: schema.minItems,
1703
- max: schema.maxItems,
1704
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1543
+ primitive: enumPrimitiveType,
1544
+ namedEnumValues: uniqueValues.map((value, index) => ({
1545
+ name: String(rawEnumNames?.[index] ?? value),
1546
+ value,
1547
+ primitive: enumPrimitiveType,
1548
+ description: rawEnumDescriptions?.[index]
1549
+ })).filter((entry) => {
1550
+ if (seenNames.has(entry.name)) return false;
1551
+ seenNames.add(entry.name);
1552
+ return true;
1553
+ })
1705
1554
  });
1706
1555
  }
1707
- /**
1708
- * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1709
- */
1710
- function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1711
- const rawItems = schema.items;
1712
- const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast.enumPropName)(null, name, options.enumSuffix) : name;
1713
- const items = rawItems ? [parseSchema({
1714
- schema: rawItems,
1715
- name: itemName
1716
- }, rawOptions)] : [];
1717
- return _kubb_ast.ast.factory.createSchema({
1718
- type: "array",
1719
- primitive: "array",
1720
- items,
1721
- min: schema.minItems,
1722
- max: schema.maxItems,
1723
- unique: schema.uniqueItems ?? void 0,
1724
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1556
+ return _kubb_ast.ast.factory.createSchema({
1557
+ ...enumBase,
1558
+ enumValues: [...new Set(filteredValues)]
1559
+ });
1560
+ }
1561
+ /**
1562
+ * Converts an object-like schema into an `ObjectSchemaNode`.
1563
+ */
1564
+ function convertObject({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1565
+ const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1566
+ const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1567
+ const resolvedPropSchema = propSchema;
1568
+ const propNullable = isNullable(resolvedPropSchema);
1569
+ const schemaNode = nameEnums(parse({
1570
+ schema: resolvedPropSchema,
1571
+ name: (0, _kubb_ast.childName)(name, propName)
1572
+ }, rawOptions), {
1573
+ parentName: name,
1574
+ propName,
1575
+ enumSuffix: options.enumSuffix
1725
1576
  });
1726
- }
1727
- /**
1728
- * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1729
- */
1730
- function convertString({ schema, name, nullable, defaultValue }) {
1731
- return _kubb_ast.ast.factory.createSchema({
1732
- type: "string",
1733
- primitive: "string",
1734
- min: schema.minLength,
1735
- max: schema.maxLength,
1736
- pattern: schema.pattern,
1737
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1577
+ return _kubb_ast.ast.factory.createProperty({
1578
+ name: propName,
1579
+ schema: {
1580
+ ...schemaNode,
1581
+ nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1582
+ },
1583
+ required
1738
1584
  });
1585
+ }) : [];
1586
+ const additionalProperties = schema.additionalProperties;
1587
+ const additionalPropertiesNode = (() => {
1588
+ if (additionalProperties === true) return true;
1589
+ if (additionalProperties === false) return false;
1590
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parse({ schema: additionalProperties }, rawOptions);
1591
+ if (additionalProperties) return _kubb_ast.ast.factory.createSchema({ type: options.unknownType });
1592
+ })();
1593
+ const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1594
+ const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_ast.ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: patternSchema }, rawOptions)])) : void 0;
1595
+ const objectNode = _kubb_ast.ast.factory.createSchema({
1596
+ type: "object",
1597
+ primitive: "object",
1598
+ properties,
1599
+ additionalProperties: additionalPropertiesNode,
1600
+ patternProperties,
1601
+ minProperties: schema.minProperties,
1602
+ maxProperties: schema.maxProperties,
1603
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1604
+ });
1605
+ if (isDiscriminator(schema) && schema.discriminator.mapping) {
1606
+ const discPropName = schema.discriminator.propertyName;
1607
+ const values = Object.keys(schema.discriminator.mapping);
1608
+ const enumName = name ? (0, _kubb_ast.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
1609
+ return _kubb_ast.ast.applyMacros(objectNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1610
+ propertyName: discPropName,
1611
+ values,
1612
+ enumName
1613
+ })], { depth: "shallow" });
1739
1614
  }
1740
- /**
1741
- * Converts a `type: 'number'` or `type: 'integer'` schema.
1742
- */
1743
- function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1744
- return _kubb_ast.ast.factory.createSchema({
1745
- type,
1746
- primitive: type,
1747
- min: schema.minimum,
1748
- max: schema.maximum,
1749
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1750
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1751
- multipleOf: schema.multipleOf,
1752
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1753
- });
1615
+ return objectNode;
1616
+ }
1617
+ /**
1618
+ * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1619
+ */
1620
+ function convertTuple({ schema, name, nullable, defaultValue, rawOptions, parse }) {
1621
+ const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }, rawOptions));
1622
+ const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? _kubb_ast.ast.factory.createSchema({ type: "any" }) : parse({ schema: schema.items }, rawOptions);
1623
+ return _kubb_ast.ast.factory.createSchema({
1624
+ type: "tuple",
1625
+ primitive: "array",
1626
+ items: tupleItems,
1627
+ rest,
1628
+ min: schema.minItems,
1629
+ max: schema.maxItems,
1630
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1631
+ });
1632
+ }
1633
+ /**
1634
+ * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1635
+ */
1636
+ function convertArray({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1637
+ const rawItems = schema.items;
1638
+ const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast.enumPropName)(null, name, options.enumSuffix) : name;
1639
+ const items = rawItems ? [parse({
1640
+ schema: rawItems,
1641
+ name: itemName
1642
+ }, rawOptions)] : [];
1643
+ return _kubb_ast.ast.factory.createSchema({
1644
+ type: "array",
1645
+ primitive: "array",
1646
+ items,
1647
+ min: schema.minItems,
1648
+ max: schema.maxItems,
1649
+ unique: schema.uniqueItems ?? void 0,
1650
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1651
+ });
1652
+ }
1653
+ /**
1654
+ * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1655
+ */
1656
+ function convertString({ schema, name, nullable, defaultValue }) {
1657
+ return _kubb_ast.ast.factory.createSchema({
1658
+ type: "string",
1659
+ primitive: "string",
1660
+ min: schema.minLength,
1661
+ max: schema.maxLength,
1662
+ pattern: schema.pattern,
1663
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1664
+ });
1665
+ }
1666
+ /**
1667
+ * Converts a `type: 'number'` or `type: 'integer'` schema.
1668
+ */
1669
+ function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1670
+ return _kubb_ast.ast.factory.createSchema({
1671
+ type,
1672
+ primitive: type,
1673
+ min: schema.minimum,
1674
+ max: schema.maximum,
1675
+ exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1676
+ exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1677
+ multipleOf: schema.multipleOf,
1678
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1679
+ });
1680
+ }
1681
+ /**
1682
+ * Converts a `type: 'boolean'` schema.
1683
+ */
1684
+ function convertBoolean({ schema, name, nullable, defaultValue }) {
1685
+ return _kubb_ast.ast.factory.createSchema({
1686
+ type: "boolean",
1687
+ primitive: "boolean",
1688
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1689
+ });
1690
+ }
1691
+ /**
1692
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1693
+ * into a `blob` node.
1694
+ */
1695
+ function convertBinary({ schema, name, nullable, defaultValue }) {
1696
+ return _kubb_ast.ast.factory.createSchema({
1697
+ type: "blob",
1698
+ primitive: "string",
1699
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1700
+ });
1701
+ }
1702
+ /**
1703
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1704
+ *
1705
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parse`
1706
+ * falls through and handles it as that single type with nullability already folded in.
1707
+ */
1708
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions, parse }) {
1709
+ const types = schema.type;
1710
+ const nonNullTypes = types.filter((t) => t !== "null");
1711
+ if (nonNullTypes.length <= 1) return null;
1712
+ const arrayNullable = types.includes("null") || nullable || void 0;
1713
+ return _kubb_ast.ast.factory.createSchema({
1714
+ type: "union",
1715
+ members: nonNullTypes.map((t) => {
1716
+ return parse({
1717
+ schema: {
1718
+ ...schema,
1719
+ type: t
1720
+ },
1721
+ name
1722
+ }, rawOptions);
1723
+ }),
1724
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1725
+ });
1726
+ }
1727
+ /**
1728
+ * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1729
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1730
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1731
+ * match/convert/fall-through contract.
1732
+ */
1733
+ const schemaRules = [
1734
+ {
1735
+ match: ({ schema }) => isReference(schema),
1736
+ convert: convertRef
1737
+ },
1738
+ {
1739
+ match: ({ schema }) => !!schema.allOf?.length,
1740
+ convert: convertAllOf
1741
+ },
1742
+ {
1743
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1744
+ convert: convertUnion
1745
+ },
1746
+ {
1747
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1748
+ convert: convertConst
1749
+ },
1750
+ {
1751
+ match: ({ schema }) => !!schema.format,
1752
+ convert: convertFormat
1753
+ },
1754
+ {
1755
+ match: ({ schema }) => isBinary(schema),
1756
+ convert: convertBinary
1757
+ },
1758
+ {
1759
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1760
+ convert: convertMultiType
1761
+ },
1762
+ {
1763
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1764
+ convert: convertString
1765
+ },
1766
+ {
1767
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1768
+ convert: (ctx) => convertNumeric(ctx, "number")
1769
+ },
1770
+ {
1771
+ match: ({ schema }) => !!schema.enum?.length,
1772
+ convert: convertEnum
1773
+ },
1774
+ {
1775
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1776
+ convert: convertObject
1777
+ },
1778
+ {
1779
+ match: ({ schema }) => "prefixItems" in schema,
1780
+ convert: convertTuple
1781
+ },
1782
+ {
1783
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1784
+ convert: convertArray
1785
+ },
1786
+ {
1787
+ match: ({ type }) => type === "string",
1788
+ convert: convertString
1789
+ },
1790
+ {
1791
+ match: ({ type }) => type === "number",
1792
+ convert: (ctx) => convertNumeric(ctx, "number")
1793
+ },
1794
+ {
1795
+ match: ({ type }) => type === "integer",
1796
+ convert: (ctx) => convertNumeric(ctx, "integer")
1797
+ },
1798
+ {
1799
+ match: ({ type }) => type === "boolean",
1800
+ convert: convertBoolean
1801
+ },
1802
+ {
1803
+ match: ({ type }) => type === "null",
1804
+ convert: ({ schema, name, nullable }) => createNullNode(schema, name, nullable)
1754
1805
  }
1806
+ ];
1807
+ //#endregion
1808
+ //#region src/parser.ts
1809
+ /**
1810
+ * Creates the schema and operation converters bound to one OpenAPI document.
1811
+ *
1812
+ * Owns the per-instance `$ref` state (cycle detection, resolved-node cache, existence cache) and
1813
+ * the `parseSchema` recursion seam, then dispatches each schema through the ordered `schemaRules`
1814
+ * table from `converters.ts`. Every converter is a standalone function that recurses through the
1815
+ * `parse` function passed to it, so this file only wires state to the converters.
1816
+ *
1817
+ * @internal
1818
+ */
1819
+ function createSchemaParser(ctx) {
1820
+ const document = ctx.document;
1755
1821
  /**
1756
- * Converts a `type: 'boolean'` schema.
1822
+ * Tracks `$ref` paths that are currently being resolved to prevent infinite
1823
+ * recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
1757
1824
  */
1758
- function convertBoolean({ schema, name, nullable, defaultValue }) {
1759
- return _kubb_ast.ast.factory.createSchema({
1760
- type: "boolean",
1761
- primitive: "boolean",
1762
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1763
- });
1764
- }
1825
+ const resolvingRefs = /* @__PURE__ */ new Set();
1765
1826
  /**
1766
- * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1767
- * into a `blob` node.
1827
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1828
+ *
1829
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1830
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1831
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1832
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1768
1833
  */
1769
- function convertBlob({ schema, name, nullable, defaultValue }) {
1770
- return _kubb_ast.ast.factory.createSchema({
1771
- type: "blob",
1772
- primitive: "string",
1773
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1774
- });
1775
- }
1834
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1776
1835
  /**
1777
- * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1778
- *
1779
- * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1780
- * falls through and handles it as that single type with nullability already folded in.
1836
+ * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1837
+ * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1838
+ * goes `false` for a `$ref` that points at a component the spec never declares.
1781
1839
  */
1782
- function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1783
- const types = schema.type;
1784
- const nonNullTypes = types.filter((t) => t !== "null");
1785
- if (nonNullTypes.length <= 1) return null;
1786
- const arrayNullable = types.includes("null") || nullable || void 0;
1787
- return _kubb_ast.ast.factory.createSchema({
1788
- type: "union",
1789
- members: nonNullTypes.map((t) => parseSchema({
1790
- schema: {
1791
- ...schema,
1792
- type: t
1793
- },
1794
- name
1795
- }, rawOptions)),
1796
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1797
- });
1840
+ const refExistence = /* @__PURE__ */ new Map();
1841
+ function refExists(refPath) {
1842
+ if (!refExistence.has(refPath)) {
1843
+ let exists = false;
1844
+ try {
1845
+ exists = !!resolveRef(document, refPath);
1846
+ } catch {
1847
+ exists = false;
1848
+ }
1849
+ refExistence.set(refPath, exists);
1850
+ }
1851
+ return refExistence.get(refPath) ?? false;
1798
1852
  }
1799
1853
  /**
1800
- * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1801
- * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1802
- * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1803
- * match/convert/fall-through contract.
1854
+ * Resolves a `$ref` to its parsed node, guarding against cycles and memoizing per instance.
1855
+ * Returns `null` when the ref is currently being resolved (a cycle) or cannot be resolved
1856
+ * (e.g. a minimal document in a unit test).
1804
1857
  */
1805
- const schemaRules = [
1806
- {
1807
- match: ({ schema }) => isReference(schema),
1808
- convert: convertRef
1809
- },
1810
- {
1811
- match: ({ schema }) => !!schema.allOf?.length,
1812
- convert: convertAllOf
1813
- },
1814
- {
1815
- match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1816
- convert: convertUnion
1817
- },
1818
- {
1819
- match: ({ schema }) => "const" in schema && schema.const !== void 0,
1820
- convert: convertConst
1821
- },
1822
- {
1823
- match: ({ schema }) => !!schema.format,
1824
- convert: convertFormat
1825
- },
1826
- {
1827
- match: ({ schema }) => isBinary(schema),
1828
- convert: convertBlob
1829
- },
1830
- {
1831
- match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1832
- convert: convertMultiType
1833
- },
1834
- {
1835
- match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1836
- convert: convertString
1837
- },
1838
- {
1839
- match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1840
- convert: (ctx) => convertNumeric(ctx, "number")
1841
- },
1842
- {
1843
- match: ({ schema }) => !!schema.enum?.length,
1844
- convert: convertEnum
1845
- },
1846
- {
1847
- match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1848
- convert: convertObject
1849
- },
1850
- {
1851
- match: ({ schema }) => "prefixItems" in schema,
1852
- convert: convertTuple
1853
- },
1854
- {
1855
- match: ({ schema, type }) => type === "array" || "items" in schema,
1856
- convert: convertArray
1857
- },
1858
- {
1859
- match: ({ type }) => type === "string",
1860
- convert: convertString
1861
- },
1862
- {
1863
- match: ({ type }) => type === "number",
1864
- convert: (ctx) => convertNumeric(ctx, "number")
1865
- },
1866
- {
1867
- match: ({ type }) => type === "integer",
1868
- convert: (ctx) => convertNumeric(ctx, "integer")
1869
- },
1870
- {
1871
- match: ({ type }) => type === "boolean",
1872
- convert: convertBoolean
1873
- },
1874
- {
1875
- match: ({ type }) => type === "null",
1876
- convert: ({ schema, name, nullable }) => createNullSchema(schema, name, nullable)
1858
+ function resolveRefNode(refPath, rawOptions) {
1859
+ if (resolvingRefs.has(refPath)) return null;
1860
+ if (!resolvedRefCache.has(refPath)) {
1861
+ let resolved = null;
1862
+ try {
1863
+ const referenced = resolveRef(document, refPath);
1864
+ if (referenced) {
1865
+ resolvingRefs.add(refPath);
1866
+ resolved = parseSchema({ schema: referenced }, rawOptions);
1867
+ resolvingRefs.delete(refPath);
1868
+ }
1869
+ } catch {}
1870
+ resolvedRefCache.set(refPath, resolved);
1877
1871
  }
1878
- ];
1872
+ return resolvedRefCache.get(refPath) ?? null;
1873
+ }
1879
1874
  /**
1880
1875
  * Converts an OAS `SchemaObject` into a `SchemaNode`.
1881
1876
  *
@@ -1894,18 +1889,22 @@ function createSchemaParser(ctx) {
1894
1889
  name
1895
1890
  }, rawOptions);
1896
1891
  const nullable = isNullable(schema) || void 0;
1897
- const schemaCtx = {
1892
+ const context = {
1898
1893
  schema,
1899
1894
  name,
1900
1895
  nullable,
1901
1896
  defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1902
1897
  type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1903
1898
  rawOptions,
1904
- options
1899
+ options,
1900
+ parse: parseSchema,
1901
+ document,
1902
+ resolveRefNode,
1903
+ refExists
1905
1904
  };
1906
1905
  for (const rule of schemaRules) {
1907
- if (!rule.match(schemaCtx)) continue;
1908
- const node = rule.convert(schemaCtx);
1906
+ if (!rule.match(context)) continue;
1907
+ const node = rule.convert(context);
1909
1908
  if (node) return node;
1910
1909
  }
1911
1910
  const emptyType = options.emptySchemaType;