@kubb/adapter-oas 5.0.0-alpha.42 → 5.0.0-alpha.44

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
@@ -25,17 +25,12 @@ let _kubb_core = require("@kubb/core");
25
25
  let node_path = require("node:path");
26
26
  node_path = __toESM(node_path, 1);
27
27
  let _redocly_openapi_core = require("@redocly/openapi-core");
28
- let _stoplight_yaml = require("@stoplight/yaml");
29
- _stoplight_yaml = __toESM(_stoplight_yaml, 1);
30
28
  let oas_normalize = require("oas-normalize");
31
29
  oas_normalize = __toESM(oas_normalize, 1);
32
- let remeda = require("remeda");
33
30
  let swagger2openapi = require("swagger2openapi");
34
31
  swagger2openapi = __toESM(swagger2openapi, 1);
35
32
  let oas = require("oas");
36
33
  oas = __toESM(oas, 1);
37
- let jsonpointer = require("jsonpointer");
38
- jsonpointer = __toESM(jsonpointer, 1);
39
34
  let oas_types = require("oas/types");
40
35
  let oas_utils = require("oas/utils");
41
36
  //#region src/constants.ts
@@ -58,6 +53,17 @@ const DEFAULT_PARSER_OPTIONS = {
58
53
  enumSuffix: "enum"
59
54
  };
60
55
  /**
56
+ * JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
57
+ *
58
+ * Used when building or parsing `$ref` strings.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
63
+ * ```
64
+ */
65
+ const SCHEMA_REF_PREFIX = "#/components/schemas/";
66
+ /**
61
67
  * OpenAPI version string written into the stub document created during multi-spec merges.
62
68
  */
63
69
  const MERGE_OPENAPI_VERSION = "3.0.0";
@@ -286,6 +292,40 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
286
292
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
287
293
  }
288
294
  //#endregion
295
+ //#region ../../internals/utils/src/object.ts
296
+ /**
297
+ * Returns `true` when `value` is a plain (non-null, non-array) object.
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * isPlainObject({}) // true
302
+ * isPlainObject([]) // false
303
+ * isPlainObject(null) // false
304
+ * ```
305
+ */
306
+ function isPlainObject(value) {
307
+ return typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
308
+ }
309
+ /**
310
+ * Recursively merges `source` into `target`, combining nested plain objects.
311
+ * Arrays and non-object values from `source` override the corresponding values in `target`.
312
+ *
313
+ * @example
314
+ * ```ts
315
+ * mergeDeep({ a: { x: 1 } }, { a: { y: 2 } })
316
+ * // { a: { x: 1, y: 2 } }
317
+ * ```
318
+ */
319
+ function mergeDeep(target, source) {
320
+ const result = { ...target };
321
+ for (const key of Object.keys(source)) {
322
+ const sv = source[key];
323
+ const tv = result[key];
324
+ result[key] = sv !== null && typeof sv === "object" && !Array.isArray(sv) && tv !== null && typeof tv === "object" && !Array.isArray(tv) ? mergeDeep(tv, sv) : sv;
325
+ }
326
+ return result;
327
+ }
328
+ //#endregion
289
329
  //#region ../../internals/utils/src/reserved.ts
290
330
  /**
291
331
  * Returns `true` when `name` is a syntactically valid JavaScript variable name.
@@ -464,7 +504,7 @@ var URLPath = class {
464
504
  * ```
465
505
  */
466
506
  function isOpenApiV2Document(doc) {
467
- return !!doc && (0, remeda.isPlainObject)(doc) && !("openapi" in doc);
507
+ return !!doc && isPlainObject(doc) && !("openapi" in doc);
468
508
  }
469
509
  /**
470
510
  * Returns `true` when a schema should be treated as nullable.
@@ -548,7 +588,7 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
548
588
  /**
549
589
  * Deep-merges multiple OpenAPI documents into a single `Document`.
550
590
  *
551
- * Each document is parsed independently then recursively merged with `remeda`'s `mergeDeep`.
591
+ * Each document is parsed independently then recursively merged via `mergeDeep` from `@internals/utils`.
552
592
  * Throws when the input array is empty.
553
593
  *
554
594
  * @example
@@ -572,7 +612,7 @@ async function mergeDocuments(pathOrApi) {
572
612
  paths: {},
573
613
  components: { schemas: {} }
574
614
  };
575
- return parseDocument(documents.reduce((acc, current) => (0, remeda.mergeDeep)(acc, current), seed));
615
+ return parseDocument(documents.reduce((acc, current) => mergeDeep(acc, current), seed));
576
616
  }
577
617
  /**
578
618
  * Creates a `Document` from an `AdapterSource`.
@@ -591,11 +631,7 @@ async function mergeDocuments(pathOrApi) {
591
631
  function parseFromConfig(source) {
592
632
  if (source.type === "data") {
593
633
  if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
594
- try {
595
- return parseDocument(_stoplight_yaml.default.parse(source.data));
596
- } catch {
597
- return parseDocument(source.data);
598
- }
634
+ return parseDocument(source.data, { canBundle: false });
599
635
  }
600
636
  if (source.type === "paths") return mergeDocuments(source.paths);
601
637
  if (new URLPath(source.path).isURL) return parseDocument(source.path);
@@ -638,7 +674,7 @@ function resolveRef(document, $ref) {
638
674
  if ($ref === "") return null;
639
675
  if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
640
676
  else return null;
641
- const current = jsonpointer.default.get(document, $ref);
677
+ const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
642
678
  if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
643
679
  return current;
644
680
  }
@@ -749,12 +785,11 @@ function getResponseBody(responseBody, contentType) {
749
785
  }
750
786
  let availableContentType;
751
787
  const contentTypes = Object.keys(body.content);
752
- contentTypes.forEach((mt) => {
753
- if (!availableContentType && oas_utils.matchesMimeType.json(mt)) availableContentType = mt;
754
- });
755
- if (!availableContentType) contentTypes.forEach((mt) => {
756
- if (!availableContentType) availableContentType = mt;
757
- });
788
+ for (const mt of contentTypes) if (oas_utils.matchesMimeType.json(mt)) {
789
+ availableContentType = mt;
790
+ break;
791
+ }
792
+ if (!availableContentType) availableContentType = contentTypes[0];
758
793
  if (availableContentType) return [
759
794
  availableContentType,
760
795
  body.content[availableContentType],
@@ -774,11 +809,13 @@ function getResponseBody(responseBody, contentType) {
774
809
  * ```
775
810
  */
776
811
  function getResponseSchema(document, operation, statusCode, options = {}) {
777
- if (operation.schema.responses) Object.keys(operation.schema.responses).forEach((key) => {
778
- const schema = operation.schema.responses[key];
779
- const $ref = isReference(schema) ? schema.$ref : void 0;
780
- if (schema && $ref) operation.schema.responses[key] = resolveRef(document, $ref);
781
- });
812
+ if (operation.schema.responses) {
813
+ const responses = operation.schema.responses;
814
+ for (const key in responses) {
815
+ const schema = responses[key];
816
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
817
+ }
818
+ }
782
819
  const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType);
783
820
  if (responseBody === false) return {};
784
821
  const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
@@ -817,14 +854,24 @@ function getRequestSchema(document, operation, options = {}) {
817
854
  * // returned unchanged — contains a $ref
818
855
  * ```
819
856
  */
857
+ /**
858
+ * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
859
+ * structurally significant on its own (see `structuralKeys`).
860
+ *
861
+ * A fragment with a structural keyword can't be safely merged into a parent schema.
862
+ */
863
+ function hasStructuralKeywords(fragment) {
864
+ for (const key in fragment) if (structuralKeys.has(key)) return true;
865
+ return false;
866
+ }
820
867
  function flattenSchema(schema) {
821
868
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
822
- if (schema.allOf.some((item) => (0, oas_types.isRef)(item))) return schema;
823
- const isPlainFragment = (item) => !Object.keys(item).some((key) => structuralKeys.has(key));
824
- if (!schema.allOf.every((item) => isPlainFragment(item))) return schema;
869
+ const allOfFragments = schema.allOf;
870
+ if (allOfFragments.some((item) => (0, oas_types.isRef)(item))) return schema;
871
+ if (allOfFragments.some(hasStructuralKeywords)) return schema;
825
872
  const merged = { ...schema };
826
873
  delete merged.allOf;
827
- for (const fragment of schema.allOf) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
874
+ for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
828
875
  return merged;
829
876
  }
830
877
  /**
@@ -854,10 +901,15 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
854
901
  for (const item of schema) collectRefs(item, refs);
855
902
  return refs;
856
903
  }
857
- if (schema && typeof schema === "object") for (const [key, value] of Object.entries(schema)) if (key === "$ref" && typeof value === "string") {
858
- const match = value.match(/^#\/components\/schemas\/(.+)$/);
859
- if (match) refs.add(match[1]);
860
- } else collectRefs(value, refs);
904
+ if (schema && typeof schema === "object") for (const key in schema) {
905
+ const value = schema[key];
906
+ if (key === "$ref" && typeof value === "string") {
907
+ if (value.startsWith("#/components/schemas/")) {
908
+ const name = value.slice(21);
909
+ if (name) refs.add(name);
910
+ }
911
+ } else collectRefs(value, refs);
912
+ }
861
913
  return refs;
862
914
  }
863
915
  /**
@@ -941,13 +993,23 @@ function getSchemas(document, { contentType }) {
941
993
  }
942
994
  const schemas = {};
943
995
  const nameMapping = /* @__PURE__ */ new Map();
944
- const multipleSources = (items) => new Set(items.map((i) => i.source)).size > 1;
945
- for (const [, items] of normalizedNames) items.forEach((item, index) => {
946
- const suffix = items.length === 1 ? "" : multipleSources(items) ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
947
- const uniqueName = item.originalName + suffix;
948
- schemas[uniqueName] = item.schema;
949
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
950
- });
996
+ for (const [, items] of normalizedNames) {
997
+ const isSingle = items.length === 1;
998
+ let hasMultipleSources = false;
999
+ if (!isSingle) {
1000
+ const firstSource = items[0].source;
1001
+ for (let i = 1; i < items.length; i++) if (items[i].source !== firstSource) {
1002
+ hasMultipleSources = true;
1003
+ break;
1004
+ }
1005
+ }
1006
+ items.forEach((item, index) => {
1007
+ const suffix = isSingle ? "" : hasMultipleSources ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
1008
+ const uniqueName = item.originalName + suffix;
1009
+ schemas[uniqueName] = item.schema;
1010
+ nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
1011
+ });
1012
+ }
951
1013
  return {
952
1014
  schemas: sortSchemas(schemas),
953
1015
  nameMapping
@@ -1094,7 +1156,7 @@ function createSchemaParser(ctx) {
1094
1156
  if (!deref || !isDiscriminator(deref)) return true;
1095
1157
  const parentUnion = deref.oneOf ?? deref.anyOf;
1096
1158
  if (!parentUnion) return true;
1097
- const childRef = `#/components/schemas/${name}`;
1159
+ const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1098
1160
  const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1099
1161
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1100
1162
  if (inOneOf || inMapping) {
@@ -1323,15 +1385,21 @@ function createSchemaParser(ctx) {
1323
1385
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1324
1386
  if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1325
1387
  const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1326
- const sourceValues = extensionKey ? [...new Set(schema[extensionKey])] : [...new Set(filteredValues)];
1388
+ const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1389
+ const uniqueValues = [...new Set(filteredValues)];
1390
+ const seenNames = /* @__PURE__ */ new Set();
1327
1391
  return _kubb_core.ast.createSchema({
1328
1392
  ...enumBase,
1329
1393
  primitive: enumPrimitiveType,
1330
- namedEnumValues: sourceValues.map((label, index) => ({
1331
- name: String(label),
1332
- value: extensionKey ? filteredValues[index] ?? label : label,
1394
+ namedEnumValues: uniqueValues.map((value, index) => ({
1395
+ name: String(rawEnumNames?.[index] ?? value),
1396
+ value,
1333
1397
  primitive: enumPrimitiveType
1334
- }))
1398
+ })).filter((entry) => {
1399
+ if (seenNames.has(entry.name)) return false;
1400
+ seenNames.add(entry.name);
1401
+ return true;
1402
+ })
1335
1403
  });
1336
1404
  }
1337
1405
  return _kubb_core.ast.createSchema({
@@ -1583,41 +1651,70 @@ function createSchemaParser(ctx) {
1583
1651
  });
1584
1652
  }
1585
1653
  /**
1654
+ * Reads the inline `requestBody` metadata (description / required / contentType) that OAS exposes
1655
+ * outside the schema itself. Returns an empty object when the request body is missing or a `$ref`.
1656
+ */
1657
+ function getRequestBodyMeta(operation) {
1658
+ const body = operation.schema.requestBody;
1659
+ if (!body || isReference(body)) return { required: false };
1660
+ const inline = body;
1661
+ return {
1662
+ description: inline.description,
1663
+ required: inline.required === true,
1664
+ contentType: inline.content ? Object.keys(inline.content)[0] : void 0
1665
+ };
1666
+ }
1667
+ /**
1668
+ * Reads the inline response object (not a `$ref`) and returns its description plus its `content` map.
1669
+ */
1670
+ function getResponseMeta(responseObj) {
1671
+ if (typeof responseObj !== "object" || responseObj === null || Array.isArray(responseObj)) return {};
1672
+ const inline = responseObj;
1673
+ return {
1674
+ description: inline.description,
1675
+ content: inline.content
1676
+ };
1677
+ }
1678
+ /**
1679
+ * Collects property names whose schema has a truthy boolean flag (`readOnly` or `writeOnly`).
1680
+ * `$ref` entries are skipped since their flags live on the dereferenced target.
1681
+ */
1682
+ function collectPropertyKeysByFlag(schema, flag) {
1683
+ if (!schema?.properties) return void 0;
1684
+ const keys = [];
1685
+ for (const key in schema.properties) {
1686
+ const prop = schema.properties[key];
1687
+ if (prop && !isReference(prop) && prop[flag]) keys.push(key);
1688
+ }
1689
+ return keys.length ? keys : void 0;
1690
+ }
1691
+ /**
1586
1692
  * Converts an OAS `Operation` into an `OperationNode`.
1587
1693
  */
1588
1694
  function parseOperation(options, operation) {
1589
1695
  const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
1590
1696
  const requestBodySchema = getRequestSchema(document, operation, { contentType: ctx.contentType });
1591
1697
  const requestBodySchemaNode = requestBodySchema ? parseSchema({ schema: requestBodySchema }, options) : void 0;
1592
- const requestBodyDescription = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.description : void 0;
1593
- const requestBodyKeysToOmit = requestBodySchema?.properties ? Object.entries(requestBodySchema.properties).filter(([, prop]) => !isReference(prop) && prop.readOnly).map(([key]) => key) : void 0;
1594
- const requestBodyRequired = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.required === true : false;
1595
- const requestBodyContentType = (() => {
1596
- if (!operation.schema.requestBody || isReference(operation.schema.requestBody)) return;
1597
- const content = operation.schema.requestBody.content;
1598
- return content ? Object.keys(content)[0] : void 0;
1599
- })();
1698
+ const requestBodyMeta = getRequestBodyMeta(operation);
1600
1699
  const requestBody = requestBodySchemaNode ? {
1601
- description: requestBodyDescription,
1602
- schema: _kubb_core.ast.syncOptionality(requestBodySchemaNode, requestBodyRequired),
1603
- keysToOmit: requestBodyKeysToOmit?.length ? requestBodyKeysToOmit : void 0,
1604
- required: requestBodyRequired || void 0,
1605
- contentType: requestBodyContentType
1700
+ description: requestBodyMeta.description,
1701
+ schema: _kubb_core.ast.syncOptionality(requestBodySchemaNode, requestBodyMeta.required),
1702
+ keysToOmit: collectPropertyKeysByFlag(requestBodySchema, "readOnly"),
1703
+ required: requestBodyMeta.required || void 0,
1704
+ contentType: requestBodyMeta.contentType
1606
1705
  } : void 0;
1607
1706
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1608
1707
  const responseObj = operation.getResponseByStatusCode(statusCode);
1609
1708
  const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType });
1610
1709
  const schema = responseSchema && Object.keys(responseSchema).length > 0 ? parseSchema({ schema: responseSchema }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) });
1611
- const description = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : void 0;
1612
- const rawContent = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.content : void 0;
1613
- const mediaType = rawContent ? getMediaType(Object.keys(rawContent)[0] ?? "") : getMediaType(operation.contentType ?? "");
1614
- const keysToOmit = responseSchema?.properties ? Object.entries(responseSchema.properties).filter(([, prop]) => !isReference(prop) && prop.writeOnly).map(([key]) => key) : void 0;
1710
+ const { description, content } = getResponseMeta(responseObj);
1711
+ const mediaType = content ? getMediaType(Object.keys(content)[0] ?? "") : getMediaType(operation.contentType ?? "");
1615
1712
  return _kubb_core.ast.createResponse({
1616
1713
  statusCode,
1617
1714
  description,
1618
1715
  schema,
1619
1716
  mediaType,
1620
- keysToOmit: keysToOmit?.length ? keysToOmit : void 0
1717
+ keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
1621
1718
  });
1622
1719
  });
1623
1720
  const urlPath = new URLPath(operation.path);