@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 +161 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +332 -13
- package/dist/index.js +159 -60
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/constants.ts +12 -0
- package/src/factory.ts +8 -12
- package/src/guards.ts +1 -1
- package/src/index.ts +2 -2
- package/src/parser.ts +68 -54
- package/src/refs.ts +4 -2
- package/src/resolvers.ts +48 -25
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 &&
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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)
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
if (
|
|
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
|
|
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
|
|
858
|
-
const
|
|
859
|
-
if (
|
|
860
|
-
|
|
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
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
1331
|
-
name: String(
|
|
1332
|
-
value
|
|
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
|
|
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:
|
|
1602
|
-
schema: _kubb_core.ast.syncOptionality(requestBodySchemaNode,
|
|
1603
|
-
keysToOmit:
|
|
1604
|
-
required:
|
|
1605
|
-
contentType:
|
|
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
|
|
1612
|
-
const
|
|
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:
|
|
1717
|
+
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
1621
1718
|
});
|
|
1622
1719
|
});
|
|
1623
1720
|
const urlPath = new URLPath(operation.path);
|