@kubb/oas 4.18.4 → 4.19.0

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
@@ -52,6 +52,7 @@ let oas = require("oas");
52
52
  oas = __toESM(oas);
53
53
  let node_path = require("node:path");
54
54
  node_path = __toESM(node_path);
55
+ let _kubb_core_transformers = require("@kubb/core/transformers");
55
56
  let _kubb_core_utils = require("@kubb/core/utils");
56
57
  let oas_types = require("oas/types");
57
58
  let oas_normalize = require("oas-normalize");
@@ -4370,26 +4371,25 @@ function isParameterObject(obj) {
4370
4371
  return obj && "in" in obj;
4371
4372
  }
4372
4373
  /**
4373
- * Determines if a schema is nullable, considering both the standard `nullable` property and the legacy `x-nullable` extension.
4374
- *
4375
- * @param schema - The schema object to check.
4376
- * @returns `true` if the schema is marked as nullable; otherwise, `false`.
4374
+ * Determines if a schema is nullable, considering:
4375
+ * - OpenAPI 3.0 `nullable` / `x-nullable`
4376
+ * - OpenAPI 3.1 JSON Schema `type: ['null', ...]` or `type: 'null'`
4377
4377
  */
4378
4378
  function isNullable(schema) {
4379
- return schema?.nullable ?? schema?.["x-nullable"] ?? false;
4379
+ if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
4380
+ const schemaType = schema?.type;
4381
+ if (schemaType === "null") return true;
4382
+ if (Array.isArray(schemaType)) return schemaType.includes("null");
4383
+ return false;
4380
4384
  }
4381
4385
  /**
4382
4386
  * Determines if the given object is an OpenAPI ReferenceObject.
4383
- *
4384
- * @returns True if {@link obj} is a ReferenceObject; otherwise, false.
4385
4387
  */
4386
4388
  function isReference(obj) {
4387
4389
  return !!obj && (0, oas_types.isRef)(obj);
4388
4390
  }
4389
4391
  /**
4390
4392
  * Determines if the given object is a SchemaObject with a discriminator property of type DiscriminatorObject.
4391
- *
4392
- * @returns True if {@link obj} is a SchemaObject containing a non-string {@link discriminator} property.
4393
4393
  */
4394
4394
  function isDiscriminator(obj) {
4395
4395
  return !!obj && obj?.["discriminator"] && typeof obj.discriminator !== "string";
@@ -4398,9 +4398,6 @@ function isDiscriminator(obj) {
4398
4398
  * Determines whether a schema is required.
4399
4399
  *
4400
4400
  * Returns true if the schema has a non-empty {@link SchemaObject.required} array or a truthy {@link SchemaObject.required} property.
4401
- *
4402
- * @param schema - The schema object to check.
4403
- * @returns True if the schema is required; otherwise, false.
4404
4401
  */
4405
4402
  function isRequired(schema) {
4406
4403
  if (!schema) return false;
@@ -4496,6 +4493,149 @@ function parseFromConfig(config, oasClass = Oas) {
4496
4493
  if (new _kubb_core_utils.URLPath(config.input.path).isURL) return parse(config.input.path, { oasClass });
4497
4494
  return parse(node_path.default.resolve(config.root, config.input.path), { oasClass });
4498
4495
  }
4496
+ /**
4497
+ * Flatten allOf schemas by merging keyword-only fragments.
4498
+ * Only flattens schemas where allOf items don't contain structural keys or $refs.
4499
+ */
4500
+ function flattenSchema(schema) {
4501
+ if (!schema?.allOf || schema.allOf.length === 0) return schema || null;
4502
+ if (schema.allOf.some((item) => (0, oas_types.isRef)(item))) return schema;
4503
+ const isPlainFragment = (item) => !Object.keys(item).some((key) => STRUCTURAL_KEYS.has(key));
4504
+ if (!schema.allOf.every((item) => isPlainFragment(item))) return schema;
4505
+ const merged = { ...schema };
4506
+ delete merged.allOf;
4507
+ for (const fragment of schema.allOf) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
4508
+ return merged;
4509
+ }
4510
+ /**
4511
+ * Validate an OpenAPI document using oas-normalize.
4512
+ */
4513
+ async function validate(document) {
4514
+ return new oas_normalize.default(document, {
4515
+ enablePaths: true,
4516
+ colorizeErrors: true
4517
+ }).validate({ parser: { validate: { errors: { colorize: true } } } });
4518
+ }
4519
+ /**
4520
+ * Collect all schema $ref dependencies recursively.
4521
+ */
4522
+ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
4523
+ if (Array.isArray(schema)) {
4524
+ for (const item of schema) collectRefs(item, refs);
4525
+ return refs;
4526
+ }
4527
+ if (schema && typeof schema === "object") for (const [key, value] of Object.entries(schema)) if (key === "$ref" && typeof value === "string") {
4528
+ const match = value.match(/^#\/components\/schemas\/(.+)$/);
4529
+ if (match) refs.add(match[1]);
4530
+ } else collectRefs(value, refs);
4531
+ return refs;
4532
+ }
4533
+ /**
4534
+ * Sort schemas topologically so referenced schemas appear first.
4535
+ */
4536
+ function sortSchemas(schemas) {
4537
+ const deps = /* @__PURE__ */ new Map();
4538
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
4539
+ const sorted = [];
4540
+ const visited = /* @__PURE__ */ new Set();
4541
+ function visit(name, stack = /* @__PURE__ */ new Set()) {
4542
+ if (visited.has(name)) return;
4543
+ if (stack.has(name)) return;
4544
+ stack.add(name);
4545
+ const children = deps.get(name) || [];
4546
+ for (const child of children) if (deps.has(child)) visit(child, stack);
4547
+ stack.delete(name);
4548
+ visited.add(name);
4549
+ sorted.push(name);
4550
+ }
4551
+ for (const name of Object.keys(schemas)) visit(name);
4552
+ const sortedSchemas = {};
4553
+ for (const name of sorted) sortedSchemas[name] = schemas[name];
4554
+ return sortedSchemas;
4555
+ }
4556
+ /**
4557
+ * Extract schema from content object (used by responses and requestBodies).
4558
+ * Returns null if the schema is just a $ref (not a unique type definition).
4559
+ */
4560
+ function extractSchemaFromContent(content, preferredContentType) {
4561
+ if (!content) return null;
4562
+ const firstContentType = Object.keys(content)[0] || "application/json";
4563
+ const schema = content[preferredContentType || firstContentType]?.schema;
4564
+ if (schema && "$ref" in schema) return null;
4565
+ return schema || null;
4566
+ }
4567
+ /**
4568
+ * Get semantic suffix for a schema source.
4569
+ */
4570
+ function getSemanticSuffix(source) {
4571
+ switch (source) {
4572
+ case "schemas": return "Schema";
4573
+ case "responses": return "Response";
4574
+ case "requestBodies": return "Request";
4575
+ }
4576
+ }
4577
+ /**
4578
+ * Legacy resolution strategy - no collision detection, just use original names.
4579
+ * This preserves backward compatibility when collisionDetection is false.
4580
+ * @deprecated
4581
+ */
4582
+ function legacyResolve(schemasWithMeta) {
4583
+ const schemas = {};
4584
+ const nameMapping = /* @__PURE__ */ new Map();
4585
+ for (const item of schemasWithMeta) {
4586
+ schemas[item.originalName] = item.schema;
4587
+ const refPath = `#/components/${item.source}/${item.originalName}`;
4588
+ nameMapping.set(refPath, item.originalName);
4589
+ }
4590
+ return {
4591
+ schemas,
4592
+ nameMapping
4593
+ };
4594
+ }
4595
+ /**
4596
+ * Resolve name collisions by applying suffixes based on collision type.
4597
+ *
4598
+ * Strategy:
4599
+ * - Same-component collisions (e.g., "Variant" + "variant" both in schemas): numeric suffixes (Variant, Variant2)
4600
+ * - Cross-component collisions (e.g., "Pet" in schemas + "Pet" in requestBodies): semantic suffixes (PetSchema, PetRequest)
4601
+ */
4602
+ function resolveCollisions(schemasWithMeta) {
4603
+ const schemas = {};
4604
+ const nameMapping = /* @__PURE__ */ new Map();
4605
+ const normalizedNames = /* @__PURE__ */ new Map();
4606
+ for (const item of schemasWithMeta) {
4607
+ const normalized = (0, _kubb_core_transformers.pascalCase)(item.originalName);
4608
+ if (!normalizedNames.has(normalized)) normalizedNames.set(normalized, []);
4609
+ normalizedNames.get(normalized).push(item);
4610
+ }
4611
+ for (const [, items] of normalizedNames) {
4612
+ if (items.length === 1) {
4613
+ const item = items[0];
4614
+ schemas[item.originalName] = item.schema;
4615
+ const refPath = `#/components/${item.source}/${item.originalName}`;
4616
+ nameMapping.set(refPath, item.originalName);
4617
+ continue;
4618
+ }
4619
+ if (new Set(items.map((item) => item.source)).size === 1) items.forEach((item, index) => {
4620
+ const suffix = index === 0 ? "" : (index + 1).toString();
4621
+ const uniqueName = item.originalName + suffix;
4622
+ schemas[uniqueName] = item.schema;
4623
+ const refPath = `#/components/${item.source}/${item.originalName}`;
4624
+ nameMapping.set(refPath, uniqueName);
4625
+ });
4626
+ else items.forEach((item) => {
4627
+ const suffix = getSemanticSuffix(item.source);
4628
+ const uniqueName = item.originalName + suffix;
4629
+ schemas[uniqueName] = item.schema;
4630
+ const refPath = `#/components/${item.source}/${item.originalName}`;
4631
+ nameMapping.set(refPath, uniqueName);
4632
+ });
4633
+ }
4634
+ return {
4635
+ schemas,
4636
+ nameMapping
4637
+ };
4638
+ }
4499
4639
 
4500
4640
  //#endregion
4501
4641
  //#region src/Oas.ts
@@ -4664,17 +4804,17 @@ var Oas = class extends oas.default {
4664
4804
  if (!(contentType in responseBody.content)) return false;
4665
4805
  return responseBody.content[contentType];
4666
4806
  }
4667
- let availablecontentType;
4807
+ let availableContentType;
4668
4808
  const contentTypes = Object.keys(responseBody.content);
4669
4809
  contentTypes.forEach((mt) => {
4670
- if (!availablecontentType && oas_utils.matchesMimeType.json(mt)) availablecontentType = mt;
4810
+ if (!availableContentType && oas_utils.matchesMimeType.json(mt)) availableContentType = mt;
4671
4811
  });
4672
- if (!availablecontentType) contentTypes.forEach((mt) => {
4673
- if (!availablecontentType) availablecontentType = mt;
4812
+ if (!availableContentType) contentTypes.forEach((mt) => {
4813
+ if (!availableContentType) availableContentType = mt;
4674
4814
  });
4675
- if (availablecontentType) return [
4676
- availablecontentType,
4677
- responseBody.content[availablecontentType],
4815
+ if (availableContentType) return [
4816
+ availableContentType,
4817
+ responseBody.content[availableContentType],
4678
4818
  ...responseBody.description ? [responseBody.description] : []
4679
4819
  ];
4680
4820
  return false;
@@ -4746,21 +4886,61 @@ var Oas = class extends oas.default {
4746
4886
  properties: {}
4747
4887
  });
4748
4888
  }
4749
- async valdiate() {
4750
- return new (await (import("oas-normalize").then((m) => m.default)))(this.api, {
4751
- enablePaths: true,
4752
- colorizeErrors: true
4753
- }).validate({ parser: { validate: { errors: { colorize: true } } } });
4889
+ async validate() {
4890
+ return validate(this.api);
4754
4891
  }
4755
4892
  flattenSchema(schema) {
4756
- if (!schema?.allOf || schema.allOf.length === 0) return schema || null;
4757
- if (schema.allOf.some((item) => isReference(item))) return schema;
4758
- const isPlainFragment = (item) => !Object.keys(item).some((key) => STRUCTURAL_KEYS.has(key));
4759
- if (!schema.allOf.every((item) => isPlainFragment(item))) return schema;
4760
- const merged = { ...schema };
4761
- delete merged.allOf;
4762
- for (const fragment of schema.allOf) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
4763
- return merged;
4893
+ return flattenSchema(schema);
4894
+ }
4895
+ /**
4896
+ * Get schemas from OpenAPI components (schemas, responses, requestBodies).
4897
+ * Returns schemas in dependency order along with name mapping for collision resolution.
4898
+ */
4899
+ getSchemas(options = {}) {
4900
+ const contentType = options.contentType ?? this.#options.contentType;
4901
+ const includes = options.includes ?? [
4902
+ "schemas",
4903
+ "requestBodies",
4904
+ "responses"
4905
+ ];
4906
+ const shouldResolveCollisions = options.collisionDetection ?? this.#options.collisionDetection ?? false;
4907
+ const components = this.getDefinition().components;
4908
+ const schemasWithMeta = [];
4909
+ if (includes.includes("schemas")) {
4910
+ const componentSchemas = components?.schemas || {};
4911
+ for (const [name, schema] of Object.entries(componentSchemas)) schemasWithMeta.push({
4912
+ schema,
4913
+ source: "schemas",
4914
+ originalName: name
4915
+ });
4916
+ }
4917
+ if (includes.includes("responses")) {
4918
+ const responses = components?.responses || {};
4919
+ for (const [name, response] of Object.entries(responses)) {
4920
+ const schema = extractSchemaFromContent(response.content, contentType);
4921
+ if (schema) schemasWithMeta.push({
4922
+ schema,
4923
+ source: "responses",
4924
+ originalName: name
4925
+ });
4926
+ }
4927
+ }
4928
+ if (includes.includes("requestBodies")) {
4929
+ const requestBodies = components?.requestBodies || {};
4930
+ for (const [name, request] of Object.entries(requestBodies)) {
4931
+ const schema = extractSchemaFromContent(request.content, contentType);
4932
+ if (schema) schemasWithMeta.push({
4933
+ schema,
4934
+ source: "requestBodies",
4935
+ originalName: name
4936
+ });
4937
+ }
4938
+ }
4939
+ const { schemas, nameMapping } = shouldResolveCollisions ? resolveCollisions(schemasWithMeta) : legacyResolve(schemasWithMeta);
4940
+ return {
4941
+ schemas: sortSchemas(schemas),
4942
+ nameMapping
4943
+ };
4764
4944
  }
4765
4945
  };
4766
4946
 
@@ -4804,4 +4984,5 @@ Object.defineProperty(exports, 'matchesMimeType', {
4804
4984
  exports.merge = merge;
4805
4985
  exports.parse = parse;
4806
4986
  exports.parseFromConfig = parseFromConfig;
4987
+ exports.validate = validate;
4807
4988
  //# sourceMappingURL=index.cjs.map