@kubb/oas 4.18.5 → 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");
@@ -4373,9 +4374,6 @@ function isParameterObject(obj) {
4373
4374
  * Determines if a schema is nullable, considering:
4374
4375
  * - OpenAPI 3.0 `nullable` / `x-nullable`
4375
4376
  * - OpenAPI 3.1 JSON Schema `type: ['null', ...]` or `type: 'null'`
4376
- *
4377
- * @param schema - The schema object to check.
4378
- * @returns `true` if the schema is marked as nullable; otherwise, `false`.
4379
4377
  */
4380
4378
  function isNullable(schema) {
4381
4379
  if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
@@ -4386,16 +4384,12 @@ function isNullable(schema) {
4386
4384
  }
4387
4385
  /**
4388
4386
  * Determines if the given object is an OpenAPI ReferenceObject.
4389
- *
4390
- * @returns True if {@link obj} is a ReferenceObject; otherwise, false.
4391
4387
  */
4392
4388
  function isReference(obj) {
4393
4389
  return !!obj && (0, oas_types.isRef)(obj);
4394
4390
  }
4395
4391
  /**
4396
4392
  * Determines if the given object is a SchemaObject with a discriminator property of type DiscriminatorObject.
4397
- *
4398
- * @returns True if {@link obj} is a SchemaObject containing a non-string {@link discriminator} property.
4399
4393
  */
4400
4394
  function isDiscriminator(obj) {
4401
4395
  return !!obj && obj?.["discriminator"] && typeof obj.discriminator !== "string";
@@ -4404,9 +4398,6 @@ function isDiscriminator(obj) {
4404
4398
  * Determines whether a schema is required.
4405
4399
  *
4406
4400
  * Returns true if the schema has a non-empty {@link SchemaObject.required} array or a truthy {@link SchemaObject.required} property.
4407
- *
4408
- * @param schema - The schema object to check.
4409
- * @returns True if the schema is required; otherwise, false.
4410
4401
  */
4411
4402
  function isRequired(schema) {
4412
4403
  if (!schema) return false;
@@ -4502,6 +4493,149 @@ function parseFromConfig(config, oasClass = Oas) {
4502
4493
  if (new _kubb_core_utils.URLPath(config.input.path).isURL) return parse(config.input.path, { oasClass });
4503
4494
  return parse(node_path.default.resolve(config.root, config.input.path), { oasClass });
4504
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
+ }
4505
4639
 
4506
4640
  //#endregion
4507
4641
  //#region src/Oas.ts
@@ -4753,20 +4887,60 @@ var Oas = class extends oas.default {
4753
4887
  });
4754
4888
  }
4755
4889
  async validate() {
4756
- return new (await (import("oas-normalize").then((m) => m.default)))(this.api, {
4757
- enablePaths: true,
4758
- colorizeErrors: true
4759
- }).validate({ parser: { validate: { errors: { colorize: true } } } });
4890
+ return validate(this.api);
4760
4891
  }
4761
4892
  flattenSchema(schema) {
4762
- if (!schema?.allOf || schema.allOf.length === 0) return schema || null;
4763
- if (schema.allOf.some((item) => isReference(item))) return schema;
4764
- const isPlainFragment = (item) => !Object.keys(item).some((key) => STRUCTURAL_KEYS.has(key));
4765
- if (!schema.allOf.every((item) => isPlainFragment(item))) return schema;
4766
- const merged = { ...schema };
4767
- delete merged.allOf;
4768
- for (const fragment of schema.allOf) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
4769
- 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
+ };
4770
4944
  }
4771
4945
  };
4772
4946
 
@@ -4810,4 +4984,5 @@ Object.defineProperty(exports, 'matchesMimeType', {
4810
4984
  exports.merge = merge;
4811
4985
  exports.parse = parse;
4812
4986
  exports.parseFromConfig = parseFromConfig;
4987
+ exports.validate = validate;
4813
4988
  //# sourceMappingURL=index.cjs.map