@kubb/adapter-oas 5.0.0-beta.62 → 5.0.0-beta.64
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 +266 -175
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +267 -176
- package/dist/index.js.map +1 -1
- package/package.json +4 -6
- package/src/adapter.bench.ts +0 -60
- package/src/adapter.ts +0 -207
- package/src/bundler.ts +0 -71
- package/src/constants.ts +0 -136
- package/src/dialect.ts +0 -60
- package/src/discriminator.ts +0 -132
- package/src/factory.ts +0 -180
- package/src/guards.ts +0 -68
- package/src/index.ts +0 -15
- package/src/mime.ts +0 -22
- package/src/operation.ts +0 -194
- package/src/parser.ts +0 -1135
- package/src/refs.ts +0 -85
- package/src/resolvers.ts +0 -582
- package/src/schemaDiagnostics.ts +0 -76
- package/src/stream.ts +0 -264
- package/src/types.ts +0 -235
- /package/dist/{chunk-C0LytTxp.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __name } from "./
|
|
1
|
+
import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
|
|
2
2
|
import { AdapterFactoryOptions, ast } from "@kubb/core";
|
|
3
3
|
|
|
4
4
|
//#region ../../node_modules/.pnpm/@types+json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts
|
|
@@ -1250,7 +1250,7 @@ type AdapterOasResolvedOptions = {
|
|
|
1250
1250
|
enumSuffix: AdapterOasOptions['enumSuffix'];
|
|
1251
1251
|
/**
|
|
1252
1252
|
* Map from original `$ref` paths to their collision-resolved schema names.
|
|
1253
|
-
* Populated
|
|
1253
|
+
* Populated once the adapter resolves a spec's schemas, on the first `stream()` or `parse()`.
|
|
1254
1254
|
*
|
|
1255
1255
|
* @example
|
|
1256
1256
|
* ```ts
|
|
@@ -1267,7 +1267,7 @@ type AdapterOas = AdapterFactoryOptions<'oas', AdapterOasOptions, AdapterOasReso
|
|
|
1267
1267
|
//#endregion
|
|
1268
1268
|
//#region src/adapter.d.ts
|
|
1269
1269
|
/**
|
|
1270
|
-
*
|
|
1270
|
+
* The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
|
|
1271
1271
|
*/
|
|
1272
1272
|
declare const adapterOasName = "oas";
|
|
1273
1273
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import "./
|
|
1
|
+
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
2
|
import { Diagnostics, ast, createAdapter } from "@kubb/core";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { access, readFile } from "node:fs/promises";
|
|
5
5
|
import { compileErrors, validate } from "@readme/openapi-parser";
|
|
6
|
+
import { upgrade } from "@scalar/openapi-upgrader";
|
|
6
7
|
import { parse } from "yaml";
|
|
7
8
|
import { bundle } from "api-ref-bundler";
|
|
8
9
|
import { macroDiscriminatorEnum, macroEnumName, macroSimplifyUnion } from "@kubb/ast/macros";
|
|
@@ -14,10 +15,9 @@ import { collect, narrowSchema } from "@kubb/ast";
|
|
|
14
15
|
*
|
|
15
16
|
* @example
|
|
16
17
|
* ```ts
|
|
17
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
18
|
+
* import { DEFAULT_PARSER_OPTIONS, parseOas } from '@kubb/adapter-oas'
|
|
18
19
|
*
|
|
19
|
-
* const
|
|
20
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
20
|
+
* const { root } = parseOas(document, { ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
const DEFAULT_PARSER_OPTIONS = {
|
|
@@ -88,12 +88,24 @@ const structuralKeys = new Set([
|
|
|
88
88
|
"not"
|
|
89
89
|
]);
|
|
90
90
|
/**
|
|
91
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
92
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
93
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
94
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
95
|
+
*/
|
|
96
|
+
const specialCasedFormats = new Set([
|
|
97
|
+
"int64",
|
|
98
|
+
"date-time",
|
|
99
|
+
"date",
|
|
100
|
+
"time"
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
91
103
|
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
92
104
|
*
|
|
93
105
|
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
94
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
95
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
|
|
96
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
106
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
107
|
+
* separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
|
|
108
|
+
* and `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
97
109
|
*
|
|
98
110
|
* @example
|
|
99
111
|
* ```ts
|
|
@@ -104,18 +116,6 @@ const structuralKeys = new Set([
|
|
|
104
116
|
* formatMap['float'] // 'number'
|
|
105
117
|
* ```
|
|
106
118
|
*/
|
|
107
|
-
/**
|
|
108
|
-
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
109
|
-
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
110
|
-
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
111
|
-
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
112
|
-
*/
|
|
113
|
-
const specialCasedFormats = new Set([
|
|
114
|
-
"int64",
|
|
115
|
-
"date-time",
|
|
116
|
-
"date",
|
|
117
|
-
"time"
|
|
118
|
-
]);
|
|
119
119
|
const formatMap = {
|
|
120
120
|
uuid: "uuid",
|
|
121
121
|
email: "email",
|
|
@@ -295,19 +295,6 @@ async function read(path) {
|
|
|
295
295
|
//#endregion
|
|
296
296
|
//#region ../../internals/utils/src/object.ts
|
|
297
297
|
/**
|
|
298
|
-
* Returns `true` when `value` is a plain (non-null, non-array) object.
|
|
299
|
-
*
|
|
300
|
-
* @example
|
|
301
|
-
* ```ts
|
|
302
|
-
* isPlainObject({}) // true
|
|
303
|
-
* isPlainObject([]) // false
|
|
304
|
-
* isPlainObject(null) // false
|
|
305
|
-
* ```
|
|
306
|
-
*/
|
|
307
|
-
function isPlainObject(value) {
|
|
308
|
-
return typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
298
|
* Recursively merges `source` into `target`, combining nested plain objects.
|
|
312
299
|
* Arrays and non-object values from `source` override the corresponding values in `target`.
|
|
313
300
|
*
|
|
@@ -574,74 +561,14 @@ async function bundleDocument(pathOrUrl) {
|
|
|
574
561
|
return await bundle(pathOrUrl, resolver);
|
|
575
562
|
}
|
|
576
563
|
//#endregion
|
|
577
|
-
//#region src/guards.ts
|
|
578
|
-
/**
|
|
579
|
-
* Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
|
|
580
|
-
*
|
|
581
|
-
* @example
|
|
582
|
-
* ```ts
|
|
583
|
-
* if (isOpenApiV2Document(doc)) {
|
|
584
|
-
* // doc is OpenAPIV2.Document
|
|
585
|
-
* }
|
|
586
|
-
* ```
|
|
587
|
-
*/
|
|
588
|
-
function isOpenApiV2Document(doc) {
|
|
589
|
-
return !!doc && isPlainObject(doc) && !("openapi" in doc);
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Returns `true` when a schema should be treated as nullable.
|
|
593
|
-
*
|
|
594
|
-
* Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
|
|
595
|
-
* `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
|
|
596
|
-
*
|
|
597
|
-
* @example
|
|
598
|
-
* ```ts
|
|
599
|
-
* isNullable({ type: 'string', nullable: true }) // true
|
|
600
|
-
* isNullable({ type: ['string', 'null'] }) // true
|
|
601
|
-
* isNullable({ type: 'string' }) // false
|
|
602
|
-
* ```
|
|
603
|
-
*/
|
|
604
|
-
function isNullable(schema) {
|
|
605
|
-
if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
|
|
606
|
-
const schemaType = schema?.type;
|
|
607
|
-
if (schemaType === "null") return true;
|
|
608
|
-
if (Array.isArray(schemaType)) return schemaType.includes("null");
|
|
609
|
-
return false;
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
|
|
613
|
-
*
|
|
614
|
-
* @example
|
|
615
|
-
* ```ts
|
|
616
|
-
* isReference({ $ref: '#/components/schemas/Pet' }) // true
|
|
617
|
-
* isReference({ type: 'string' }) // false
|
|
618
|
-
* ```
|
|
619
|
-
*/
|
|
620
|
-
function isReference(obj) {
|
|
621
|
-
return !!obj && typeof obj === "object" && "$ref" in obj;
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
|
|
625
|
-
*
|
|
626
|
-
* @example
|
|
627
|
-
* ```ts
|
|
628
|
-
* isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
|
|
629
|
-
* isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
|
|
630
|
-
* ```
|
|
631
|
-
*/
|
|
632
|
-
function isDiscriminator(obj) {
|
|
633
|
-
const record = obj;
|
|
634
|
-
return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
|
|
635
|
-
}
|
|
636
|
-
//#endregion
|
|
637
564
|
//#region src/factory.ts
|
|
638
565
|
/**
|
|
639
|
-
* Loads and
|
|
566
|
+
* Loads and bundles an OpenAPI document, returning the raw `Document`.
|
|
640
567
|
*
|
|
641
568
|
* Accepts a file path string or an already-parsed document object. File paths and URLs are
|
|
642
569
|
* bundled via `api-ref-bundler`, hoisting external file schemas into named `components.schemas`
|
|
643
570
|
* entries so generators can emit named types and imports. Swagger 2.0 documents are
|
|
644
|
-
* automatically up-converted to OpenAPI 3.0 via
|
|
571
|
+
* automatically up-converted to OpenAPI 3.0 via `@scalar/openapi-upgrader`.
|
|
645
572
|
*
|
|
646
573
|
* @example
|
|
647
574
|
* ```ts
|
|
@@ -651,13 +578,7 @@ function isDiscriminator(obj) {
|
|
|
651
578
|
*/
|
|
652
579
|
async function parseDocument(pathOrApi, { canBundle = true } = {}) {
|
|
653
580
|
if (typeof pathOrApi === "string" && canBundle) return parseDocument(await bundleDocument(pathOrApi), { canBundle: false });
|
|
654
|
-
|
|
655
|
-
if (isOpenApiV2Document(document)) {
|
|
656
|
-
const { default: swagger2openapi } = await import("swagger2openapi");
|
|
657
|
-
const { openapi } = await swagger2openapi.convertObj(document, { anchors: true });
|
|
658
|
-
return openapi;
|
|
659
|
-
}
|
|
660
|
-
return document;
|
|
581
|
+
return upgrade(typeof pathOrApi === "string" ? parse(pathOrApi) : pathOrApi, "3.0");
|
|
661
582
|
}
|
|
662
583
|
/**
|
|
663
584
|
* Deep-merges multiple OpenAPI documents into a single `Document`.
|
|
@@ -747,17 +668,227 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
747
668
|
}
|
|
748
669
|
}
|
|
749
670
|
//#endregion
|
|
671
|
+
//#region src/dedupe.ts
|
|
672
|
+
/**
|
|
673
|
+
* Minimum occurrences before a shape is deduplicated.
|
|
674
|
+
*/
|
|
675
|
+
const MIN_OCCURRENCES = 2;
|
|
676
|
+
/**
|
|
677
|
+
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
678
|
+
* usage-slot and documentation fields that are not part of the shared type.
|
|
679
|
+
*/
|
|
680
|
+
function createRefNode(node, target) {
|
|
681
|
+
return ast.factory.createSchema({
|
|
682
|
+
type: "ref",
|
|
683
|
+
name: target.name,
|
|
684
|
+
ref: target.ref,
|
|
685
|
+
optional: node.optional,
|
|
686
|
+
nullish: node.nullish,
|
|
687
|
+
readOnly: node.readOnly,
|
|
688
|
+
writeOnly: node.writeOnly,
|
|
689
|
+
deprecated: node.deprecated,
|
|
690
|
+
description: node.description,
|
|
691
|
+
default: node.default,
|
|
692
|
+
example: node.example
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Strips usage-slot flags from an extracted definition and applies its name.
|
|
697
|
+
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
698
|
+
*/
|
|
699
|
+
function cleanDefinition(node, name) {
|
|
700
|
+
return {
|
|
701
|
+
...node,
|
|
702
|
+
name,
|
|
703
|
+
optional: void 0,
|
|
704
|
+
nullish: void 0
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Returns `true` when a node is eligible for deduplication. Only enums and objects qualify, and
|
|
709
|
+
* object shapes that take part in a circular chain are rejected so recursive structures are not
|
|
710
|
+
* extracted (which would break the cycle).
|
|
711
|
+
*/
|
|
712
|
+
function isCandidate(node, circularSchemas) {
|
|
713
|
+
if (node.type === "enum") return true;
|
|
714
|
+
if (node.type !== "object") return false;
|
|
715
|
+
if (node.name && circularSchemas.has(node.name)) return false;
|
|
716
|
+
return !containsCircularRef(node, { circularSchemas });
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Produces the name for an inline shape with no existing named component, resolving
|
|
720
|
+
* collisions against `usedNames`. Returns `null` for an unnamed shape, which stays inline.
|
|
721
|
+
*/
|
|
722
|
+
function nameFor(node, usedNames) {
|
|
723
|
+
const base = node.name;
|
|
724
|
+
if (!base) return null;
|
|
725
|
+
let name = base;
|
|
726
|
+
let counter = 2;
|
|
727
|
+
while (usedNames.has(name)) name = `${base}${counter++}`;
|
|
728
|
+
usedNames.add(name);
|
|
729
|
+
return name;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Scans a forest of schema and operation nodes and produces a {@link Plan}.
|
|
733
|
+
*
|
|
734
|
+
* A shape that occurs at least {@link MIN_OCCURRENCES} times is deduplicated: if any occurrence is
|
|
735
|
+
* a named top-level schema, the first one becomes the target (so other top-level duplicates and
|
|
736
|
+
* inline copies turn into references to it). Other top-level names with the same content are
|
|
737
|
+
* recorded as aliases. Otherwise a new definition is extracted using {@link nameFor}. The returned
|
|
738
|
+
* plan rewrites nodes against those decisions.
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```ts
|
|
742
|
+
* const dedupePlan = plan([...schemaNodes, ...operationNodes], { circularSchemas, usedNames })
|
|
743
|
+
* ```
|
|
744
|
+
*/
|
|
745
|
+
function plan(roots, context) {
|
|
746
|
+
const { circularSchemas, usedNames } = context;
|
|
747
|
+
const topLevelNodes = /* @__PURE__ */ new Set();
|
|
748
|
+
const groups = /* @__PURE__ */ new Map();
|
|
749
|
+
for (const root of roots) {
|
|
750
|
+
if (root.kind === "Schema") topLevelNodes.add(root);
|
|
751
|
+
for (const schemaNode of ast.collect(root, { schema: (node) => node })) {
|
|
752
|
+
if (!isCandidate(schemaNode, circularSchemas)) continue;
|
|
753
|
+
const signature = ast.signatureOf(schemaNode);
|
|
754
|
+
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
|
|
755
|
+
const group = groups.get(signature);
|
|
756
|
+
if (group) {
|
|
757
|
+
group.count++;
|
|
758
|
+
if (isTopLevel) group.topLevelNames.push(schemaNode.name);
|
|
759
|
+
} else groups.set(signature, {
|
|
760
|
+
count: 1,
|
|
761
|
+
representative: schemaNode,
|
|
762
|
+
topLevelNames: isTopLevel ? [schemaNode.name] : []
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const bySignature = /* @__PURE__ */ new Map();
|
|
767
|
+
const byName = /* @__PURE__ */ new Map();
|
|
768
|
+
const pendingExtractions = [];
|
|
769
|
+
for (const [signature, group] of groups) {
|
|
770
|
+
if (group.count < MIN_OCCURRENCES) continue;
|
|
771
|
+
const [firstName, ...duplicateNames] = group.topLevelNames;
|
|
772
|
+
if (firstName) {
|
|
773
|
+
const target = {
|
|
774
|
+
name: firstName,
|
|
775
|
+
ref: `${SCHEMA_REF_PREFIX}${firstName}`
|
|
776
|
+
};
|
|
777
|
+
bySignature.set(signature, target);
|
|
778
|
+
for (const duplicate of duplicateNames) byName.set(duplicate, target);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
const name = nameFor(group.representative, usedNames);
|
|
782
|
+
if (!name) continue;
|
|
783
|
+
bySignature.set(signature, {
|
|
784
|
+
name,
|
|
785
|
+
ref: `${SCHEMA_REF_PREFIX}${name}`
|
|
786
|
+
});
|
|
787
|
+
pendingExtractions.push({
|
|
788
|
+
name,
|
|
789
|
+
representative: group.representative
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
function rewrite(node, skipRootMatch) {
|
|
793
|
+
if (bySignature.size === 0 && byName.size === 0) return node;
|
|
794
|
+
const root = node;
|
|
795
|
+
return ast.transform(node, { schema(schemaNode) {
|
|
796
|
+
if (schemaNode.type === "ref") {
|
|
797
|
+
const refName = schemaNode.ref ? extractRefName(schemaNode.ref) : schemaNode.name;
|
|
798
|
+
const target = refName ? byName.get(refName) : void 0;
|
|
799
|
+
return target ? {
|
|
800
|
+
...schemaNode,
|
|
801
|
+
name: target.name,
|
|
802
|
+
ref: target.ref
|
|
803
|
+
} : void 0;
|
|
804
|
+
}
|
|
805
|
+
if (skipRootMatch && schemaNode === root) return void 0;
|
|
806
|
+
const target = bySignature.get(ast.signatureOf(schemaNode));
|
|
807
|
+
if (!target) return void 0;
|
|
808
|
+
return createRefNode(schemaNode, target);
|
|
809
|
+
} });
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
extracted: pendingExtractions.map(({ name, representative }) => cleanDefinition(rewrite(representative, true), name)),
|
|
813
|
+
apply(node) {
|
|
814
|
+
return rewrite(node, false);
|
|
815
|
+
},
|
|
816
|
+
applyTopLevel(node) {
|
|
817
|
+
const target = bySignature.get(ast.signatureOf(node));
|
|
818
|
+
if (target && target.name !== node.name) return ast.factory.createSchema({
|
|
819
|
+
type: "ref",
|
|
820
|
+
name: node.name ?? null,
|
|
821
|
+
ref: target.ref,
|
|
822
|
+
description: node.description,
|
|
823
|
+
deprecated: node.deprecated
|
|
824
|
+
});
|
|
825
|
+
return rewrite(node, true);
|
|
826
|
+
},
|
|
827
|
+
isAlias(name) {
|
|
828
|
+
return byName.has(name);
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
//#endregion
|
|
833
|
+
//#region src/guards.ts
|
|
834
|
+
/**
|
|
835
|
+
* Returns `true` when a schema should be treated as nullable.
|
|
836
|
+
*
|
|
837
|
+
* Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
|
|
838
|
+
* `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
|
|
839
|
+
*
|
|
840
|
+
* @example
|
|
841
|
+
* ```ts
|
|
842
|
+
* isNullable({ type: 'string', nullable: true }) // true
|
|
843
|
+
* isNullable({ type: ['string', 'null'] }) // true
|
|
844
|
+
* isNullable({ type: 'string' }) // false
|
|
845
|
+
* ```
|
|
846
|
+
*/
|
|
847
|
+
function isNullable(schema) {
|
|
848
|
+
if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
|
|
849
|
+
const schemaType = schema?.type;
|
|
850
|
+
if (schemaType === "null") return true;
|
|
851
|
+
if (Array.isArray(schemaType)) return schemaType.includes("null");
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```ts
|
|
859
|
+
* isReference({ $ref: '#/components/schemas/Pet' }) // true
|
|
860
|
+
* isReference({ type: 'string' }) // false
|
|
861
|
+
* ```
|
|
862
|
+
*/
|
|
863
|
+
function isReference(obj) {
|
|
864
|
+
return !!obj && typeof obj === "object" && "$ref" in obj;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
|
|
868
|
+
*
|
|
869
|
+
* @example
|
|
870
|
+
* ```ts
|
|
871
|
+
* isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
|
|
872
|
+
* isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
|
|
873
|
+
* ```
|
|
874
|
+
*/
|
|
875
|
+
function isDiscriminator(obj) {
|
|
876
|
+
const record = obj;
|
|
877
|
+
return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
|
|
878
|
+
}
|
|
879
|
+
//#endregion
|
|
750
880
|
//#region src/refs.ts
|
|
751
881
|
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
752
882
|
/**
|
|
753
883
|
* Resolves a local JSON pointer reference from a document.
|
|
754
884
|
*
|
|
755
|
-
* Accepts `#/...` refs. Returns `null` for empty or non-local
|
|
756
|
-
*
|
|
885
|
+
* Accepts `#/...` refs. Returns `null` for an empty or non-local ref. When the pointer cannot be
|
|
886
|
+
* resolved, reports a `refNotFound` diagnostic into the active build and returns `null`. Outside a
|
|
887
|
+
* build there is no sink to collect it, so it throws instead.
|
|
757
888
|
*
|
|
758
889
|
* @example
|
|
759
890
|
* ```ts
|
|
760
|
-
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
|
|
891
|
+
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
|
|
761
892
|
* ```
|
|
762
893
|
*/
|
|
763
894
|
function resolveRef(document, $ref) {
|
|
@@ -814,23 +945,6 @@ function dereferenceWithRef(document, schema) {
|
|
|
814
945
|
//#endregion
|
|
815
946
|
//#region src/dialect.ts
|
|
816
947
|
/**
|
|
817
|
-
* Derives a schema's `optional`/`nullish` flags from a parent's `required` value and the
|
|
818
|
-
* schema's own `nullable`. How "required" and "nullable" combine is OpenAPI-specific, so it
|
|
819
|
-
* lives in the dialect.
|
|
820
|
-
*
|
|
821
|
-
* - Non-required + non-nullable → `optional: true`.
|
|
822
|
-
* - Non-required + nullable → `nullish: true`.
|
|
823
|
-
* - Required → both flags cleared.
|
|
824
|
-
*/
|
|
825
|
-
function optionality(schema, required) {
|
|
826
|
-
const nullable = schema.nullable ?? false;
|
|
827
|
-
return {
|
|
828
|
-
...schema,
|
|
829
|
-
optional: !required && !nullable ? true : void 0,
|
|
830
|
-
nullish: !required && nullable ? true : void 0
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
948
|
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
835
949
|
*
|
|
836
950
|
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
@@ -856,14 +970,14 @@ const oasDialect = ast.defineDialect({
|
|
|
856
970
|
isReference,
|
|
857
971
|
isDiscriminator,
|
|
858
972
|
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
859
|
-
resolveRef
|
|
860
|
-
|
|
861
|
-
}
|
|
973
|
+
resolveRef
|
|
974
|
+
},
|
|
975
|
+
dedupe: { plan }
|
|
862
976
|
});
|
|
863
977
|
//#endregion
|
|
864
978
|
//#region src/discriminator.ts
|
|
865
979
|
/**
|
|
866
|
-
*
|
|
980
|
+
* Maps each child schema name to its discriminator patch data by scanning the given
|
|
867
981
|
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
868
982
|
*
|
|
869
983
|
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
|
@@ -979,9 +1093,8 @@ function findDiscriminator(mapping, ref) {
|
|
|
979
1093
|
/**
|
|
980
1094
|
* MIME type fragments that mark a media type as JSON-like.
|
|
981
1095
|
*
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
* `application/vnd.api+json`.
|
|
1096
|
+
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
1097
|
+
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
985
1098
|
*/
|
|
986
1099
|
const jsonMimeFragments = [
|
|
987
1100
|
"application/json",
|
|
@@ -1077,8 +1190,8 @@ function getRequestContent({ document, operation, mediaType }) {
|
|
|
1077
1190
|
return available ? [available, content[available]] : false;
|
|
1078
1191
|
}
|
|
1079
1192
|
/**
|
|
1080
|
-
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
1081
|
-
*
|
|
1193
|
+
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
1194
|
+
* when several are declared), then the first declared one, defaulting to `'application/json'`.
|
|
1082
1195
|
*/
|
|
1083
1196
|
function getRequestContentType({ document, operation }) {
|
|
1084
1197
|
const content = getRequestBodyContent({
|
|
@@ -1173,9 +1286,9 @@ function getSchemaType(format) {
|
|
|
1173
1286
|
return formatMap[format] ?? null;
|
|
1174
1287
|
}
|
|
1175
1288
|
/**
|
|
1176
|
-
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry
|
|
1177
|
-
* `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to
|
|
1178
|
-
* base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
1289
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry, plus the
|
|
1290
|
+
* `specialCasedFormats` that `convertFormat` handles directly. False means the format falls back to
|
|
1291
|
+
* the base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
1179
1292
|
* diagnostic in step with the parser as `formatMap` grows.
|
|
1180
1293
|
*/
|
|
1181
1294
|
function isHandledFormat(format) {
|
|
@@ -1193,7 +1306,7 @@ function getPrimitiveType(type) {
|
|
|
1193
1306
|
/**
|
|
1194
1307
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
1195
1308
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
1196
|
-
* `$ref`
|
|
1309
|
+
* Each `$ref` parameter is dereferenced via `dereferenceWithRef` before merging.
|
|
1197
1310
|
*
|
|
1198
1311
|
* @example
|
|
1199
1312
|
* ```ts
|
|
@@ -1294,7 +1407,10 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
1294
1407
|
* ```ts
|
|
1295
1408
|
* flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
|
|
1296
1409
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
1410
|
+
* ```
|
|
1297
1411
|
*
|
|
1412
|
+
* @example
|
|
1413
|
+
* ```ts
|
|
1298
1414
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
1299
1415
|
* // returned unchanged, contains a $ref
|
|
1300
1416
|
* ```
|
|
@@ -1988,7 +2104,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1988
2104
|
nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
|
|
1989
2105
|
},
|
|
1990
2106
|
required
|
|
1991
|
-
}
|
|
2107
|
+
});
|
|
1992
2108
|
}) : [];
|
|
1993
2109
|
const additionalProperties = schema.additionalProperties;
|
|
1994
2110
|
const additionalPropertiesNode = (() => {
|
|
@@ -2302,7 +2418,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2302
2418
|
description: param["description"] ?? schema.description
|
|
2303
2419
|
},
|
|
2304
2420
|
required
|
|
2305
|
-
}
|
|
2421
|
+
});
|
|
2306
2422
|
}
|
|
2307
2423
|
/**
|
|
2308
2424
|
* Reads the inline `requestBody` metadata (description / required) that OAS exposes
|
|
@@ -2355,7 +2471,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2355
2471
|
if (!schema) return [];
|
|
2356
2472
|
return [ast.factory.createContent({
|
|
2357
2473
|
contentType: ct,
|
|
2358
|
-
schema:
|
|
2474
|
+
schema: ast.optionality(parseSchema({
|
|
2359
2475
|
schema,
|
|
2360
2476
|
name: requestBodyName
|
|
2361
2477
|
}, options), requestBodyMeta.required),
|
|
@@ -2558,30 +2674,15 @@ function preScan({ schemas, parseSchema, parseOperation, document, parserOptions
|
|
|
2558
2674
|
}
|
|
2559
2675
|
const circularSchemas = new Set(circularNames);
|
|
2560
2676
|
const usedNames = new Set(Object.keys(schemas));
|
|
2561
|
-
dedupePlan =
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
if (node.type !== "object") return false;
|
|
2565
|
-
if (node.name && circularSchemas.has(node.name)) return false;
|
|
2566
|
-
return !containsCircularRef(node, { circularSchemas });
|
|
2567
|
-
},
|
|
2568
|
-
nameFor: (node) => {
|
|
2569
|
-
const base = node.name;
|
|
2570
|
-
if (!base) return null;
|
|
2571
|
-
let name = base;
|
|
2572
|
-
let counter = 2;
|
|
2573
|
-
while (usedNames.has(name)) name = `${base}${counter++}`;
|
|
2574
|
-
usedNames.add(name);
|
|
2575
|
-
return name;
|
|
2576
|
-
},
|
|
2577
|
-
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
|
|
2677
|
+
dedupePlan = oasDialect.dedupe.plan([...allNodes, ...operationNodes], {
|
|
2678
|
+
circularSchemas,
|
|
2679
|
+
usedNames
|
|
2578
2680
|
});
|
|
2579
|
-
for (const definition of dedupePlan.
|
|
2681
|
+
for (const definition of dedupePlan.extracted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
|
|
2580
2682
|
}
|
|
2581
|
-
const aliasNames = dedupePlan?.aliasNames;
|
|
2582
2683
|
return {
|
|
2583
2684
|
refAliasMap,
|
|
2584
|
-
enumNames:
|
|
2685
|
+
enumNames: dedupePlan ? enumNames.filter((name) => !dedupePlan.isAlias(name)) : enumNames,
|
|
2585
2686
|
circularNames,
|
|
2586
2687
|
discriminatorChildMap,
|
|
2587
2688
|
dedupePlan
|
|
@@ -2606,39 +2707,29 @@ function preScan({ schemas, parseSchema, parseOperation, document, parserOptions
|
|
|
2606
2707
|
* ```
|
|
2607
2708
|
*/
|
|
2608
2709
|
function createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
|
|
2609
|
-
const rewriteTopLevelSchema = (node) => {
|
|
2610
|
-
if (!dedupePlan) return node;
|
|
2611
|
-
const canonical = dedupePlan.canonicalBySignature.get(ast.signatureOf(node));
|
|
2612
|
-
if (canonical && canonical.name !== node.name) return ast.factory.createSchema({
|
|
2613
|
-
type: "ref",
|
|
2614
|
-
name: node.name ?? null,
|
|
2615
|
-
ref: canonical.ref,
|
|
2616
|
-
description: node.description,
|
|
2617
|
-
deprecated: node.deprecated
|
|
2618
|
-
});
|
|
2619
|
-
return ast.applyDedupe(node, dedupePlan, true);
|
|
2620
|
-
};
|
|
2621
2710
|
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2622
2711
|
return (async function* () {
|
|
2623
|
-
if (dedupePlan) for (const definition of dedupePlan.
|
|
2712
|
+
if (dedupePlan) for (const definition of dedupePlan.extracted) yield definition;
|
|
2624
2713
|
for (const [name, schema] of Object.entries(schemas)) {
|
|
2625
|
-
if (dedupePlan?.
|
|
2714
|
+
if (dedupePlan?.isAlias(name)) continue;
|
|
2626
2715
|
const alias = refAliasMap.get(name);
|
|
2627
2716
|
if (alias?.name && schemas[alias.name]) {
|
|
2628
|
-
|
|
2717
|
+
const aliasNode = {
|
|
2629
2718
|
...parseSchema({
|
|
2630
2719
|
schema: schemas[alias.name],
|
|
2631
2720
|
name: alias.name
|
|
2632
2721
|
}, parserOptions),
|
|
2633
2722
|
name
|
|
2634
|
-
}
|
|
2723
|
+
};
|
|
2724
|
+
yield dedupePlan ? dedupePlan.applyTopLevel(aliasNode) : aliasNode;
|
|
2635
2725
|
continue;
|
|
2636
2726
|
}
|
|
2637
2727
|
const parsed = parseSchema({
|
|
2638
2728
|
schema,
|
|
2639
2729
|
name
|
|
2640
2730
|
}, parserOptions);
|
|
2641
|
-
|
|
2731
|
+
const node = discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed;
|
|
2732
|
+
yield dedupePlan ? dedupePlan.applyTopLevel(node) : node;
|
|
2642
2733
|
}
|
|
2643
2734
|
})();
|
|
2644
2735
|
} };
|
|
@@ -2646,7 +2737,7 @@ function createInputStream({ schemas, parseSchema, parseOperation, document, par
|
|
|
2646
2737
|
return (async function* () {
|
|
2647
2738
|
for (const operation of getOperations(document)) {
|
|
2648
2739
|
const node = parseOperation(parserOptions, operation);
|
|
2649
|
-
if (node) yield dedupePlan ?
|
|
2740
|
+
if (node) yield dedupePlan ? dedupePlan.apply(node) : node;
|
|
2650
2741
|
}
|
|
2651
2742
|
})();
|
|
2652
2743
|
} };
|
|
@@ -2660,7 +2751,7 @@ function createInputStream({ schemas, parseSchema, parseOperation, document, par
|
|
|
2660
2751
|
//#endregion
|
|
2661
2752
|
//#region src/adapter.ts
|
|
2662
2753
|
/**
|
|
2663
|
-
*
|
|
2754
|
+
* The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
|
|
2664
2755
|
*/
|
|
2665
2756
|
const adapterOasName = "oas";
|
|
2666
2757
|
/**
|