@kubb/plugin-zod 5.0.0-beta.42 → 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 +123 -182
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +18 -20
- package/dist/index.js +117 -176
- package/dist/index.js.map +1 -1
- package/package.json +7 -15
- package/extension.yaml +0 -970
- package/src/components/Operations.tsx +0 -78
- package/src/components/Zod.tsx +0 -42
- package/src/constants.ts +0 -5
- package/src/generators/zodGenerator.tsx +0 -366
- package/src/index.ts +0 -11
- package/src/plugin.ts +0 -97
- package/src/printers/printerZod.ts +0 -362
- package/src/printers/printerZodMini.ts +0 -292
- package/src/resolvers/resolverZod.ts +0 -69
- package/src/types.ts +0 -223
- package/src/utils.ts +0 -299
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __name } from "./chunk-C0LytTxp.js";
|
|
2
2
|
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
3
|
-
import {
|
|
3
|
+
import { buildList, buildObject, caseParams, containsCircularRef, extractRefName, lazyGetter, mapSchemaItems, mapSchemaMembers, mapSchemaProperties, objectKey, stringify, stringifyObject, syncSchemaRef, toRegExpString } from "@kubb/ast/utils";
|
|
4
|
+
import { Const, File, Type, jsxRenderer } from "@kubb/renderer-jsx";
|
|
4
5
|
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
5
6
|
//#region ../../internals/utils/src/casing.ts
|
|
6
7
|
/**
|
|
@@ -13,117 +14,57 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
|
13
14
|
function toCamelOrPascal(text, pascal) {
|
|
14
15
|
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
15
16
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
16
|
-
|
|
17
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
17
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
18
18
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
22
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
23
|
-
* Segments are joined with `/` to form a file path.
|
|
24
|
-
*
|
|
25
|
-
* Only splits on dots followed by a letter so that version numbers
|
|
26
|
-
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
27
|
-
*/
|
|
28
|
-
function applyToFileParts(text, transformPart) {
|
|
29
|
-
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
30
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
21
|
* Converts `text` to camelCase.
|
|
34
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
35
22
|
*
|
|
36
|
-
* @example
|
|
37
|
-
* camelCase('hello-world')
|
|
38
|
-
*
|
|
23
|
+
* @example Word boundaries
|
|
24
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
25
|
+
*
|
|
26
|
+
* @example With a prefix
|
|
27
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
39
28
|
*/
|
|
40
|
-
function camelCase(text, {
|
|
41
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
42
|
-
prefix,
|
|
43
|
-
suffix
|
|
44
|
-
} : {}));
|
|
29
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
45
30
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
46
31
|
}
|
|
47
32
|
/**
|
|
48
33
|
* Converts `text` to PascalCase.
|
|
49
|
-
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
50
34
|
*
|
|
51
|
-
* @example
|
|
52
|
-
* pascalCase('hello-world')
|
|
53
|
-
*
|
|
35
|
+
* @example Word boundaries
|
|
36
|
+
* `pascalCase('hello-world') // 'HelloWorld'`
|
|
37
|
+
*
|
|
38
|
+
* @example With a suffix
|
|
39
|
+
* `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
|
|
54
40
|
*/
|
|
55
|
-
function pascalCase(text, {
|
|
56
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
57
|
-
prefix,
|
|
58
|
-
suffix
|
|
59
|
-
}) : camelCase(part));
|
|
41
|
+
function pascalCase(text, { prefix = "", suffix = "" } = {}) {
|
|
60
42
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
61
43
|
}
|
|
62
44
|
//#endregion
|
|
63
|
-
//#region ../../internals/utils/src/
|
|
45
|
+
//#region ../../internals/utils/src/fs.ts
|
|
64
46
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
47
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
48
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
49
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
67
50
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
function trimQuotes(text) {
|
|
73
|
-
if (text.length >= 2) {
|
|
74
|
-
const first = text[0];
|
|
75
|
-
const last = text[text.length - 1];
|
|
76
|
-
if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
|
|
77
|
-
}
|
|
78
|
-
return text;
|
|
79
|
-
}
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region ../../internals/utils/src/object.ts
|
|
82
|
-
/**
|
|
83
|
-
* Serializes a primitive value to a JSON string literal, stripping any surrounding quote characters first.
|
|
51
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
52
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
53
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
54
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
84
55
|
*
|
|
85
|
-
* @example
|
|
86
|
-
*
|
|
87
|
-
* stringify('"hello"') // '"hello"'
|
|
88
|
-
*/
|
|
89
|
-
function stringify(value) {
|
|
90
|
-
if (value === void 0 || value === null) return "\"\"";
|
|
91
|
-
return JSON.stringify(trimQuotes(value.toString()));
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Converts a plain object into a multiline key-value string suitable for embedding in generated code.
|
|
95
|
-
* Nested objects are recursively stringified with indentation.
|
|
56
|
+
* @example Nested path from a dotted name
|
|
57
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
96
58
|
*
|
|
97
|
-
* @example
|
|
98
|
-
*
|
|
99
|
-
* // 'foo: bar,\nnested: {\n a: 1\n }'
|
|
100
|
-
*/
|
|
101
|
-
function stringifyObject(value) {
|
|
102
|
-
return Object.entries(value).map(([key, val]) => {
|
|
103
|
-
if (val !== null && typeof val === "object") return `${key}: {\n ${stringifyObject(val)}\n }`;
|
|
104
|
-
return `${key}: ${val}`;
|
|
105
|
-
}).filter(Boolean).join(",\n");
|
|
106
|
-
}
|
|
107
|
-
//#endregion
|
|
108
|
-
//#region ../../internals/utils/src/regexp.ts
|
|
109
|
-
/**
|
|
110
|
-
* Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
|
|
111
|
-
* Inline flags expressed as `^(?im)` prefixes are extracted and applied to the resulting expression.
|
|
112
|
-
* Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
|
|
59
|
+
* @example PascalCase the final segment
|
|
60
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
113
61
|
*
|
|
114
|
-
* @example
|
|
115
|
-
*
|
|
116
|
-
* toRegExpString('^(?im)foo', null) // → '/foo/im'
|
|
62
|
+
* @example Suffix applied to the final segment only
|
|
63
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
117
64
|
*/
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const replacementTarget = match?.[1] ?? "";
|
|
122
|
-
const matchedFlags = match?.[2];
|
|
123
|
-
const cleaned = raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, "");
|
|
124
|
-
const { source, flags } = new RegExp(cleaned, matchedFlags);
|
|
125
|
-
if (func === null) return `/${source}/${flags}`;
|
|
126
|
-
return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
|
|
65
|
+
function toFilePath(name, caseLast = camelCase) {
|
|
66
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
67
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
127
68
|
}
|
|
128
69
|
//#endregion
|
|
129
70
|
//#region ../../internals/utils/src/reserved.ts
|
|
@@ -305,26 +246,24 @@ function resolveContentTypeVariants(entries, baseName) {
|
|
|
305
246
|
* shared default naming so every plugin groups output consistently:
|
|
306
247
|
*
|
|
307
248
|
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
308
|
-
* - other groups use
|
|
249
|
+
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
309
250
|
*
|
|
310
251
|
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
311
252
|
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
312
253
|
* per-plugin convention.
|
|
313
254
|
*
|
|
314
255
|
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
315
|
-
* @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
|
|
316
256
|
*
|
|
317
257
|
* @example
|
|
318
258
|
* ```ts
|
|
319
|
-
* createGroupConfig(group
|
|
320
|
-
* createGroupConfig(group, { suffix: 'Requests' }) // plugin-cypress, plugin-mcp
|
|
259
|
+
* createGroupConfig(group) // shared across every plugin
|
|
321
260
|
* ```
|
|
322
261
|
*/
|
|
323
|
-
function createGroupConfig(group
|
|
262
|
+
function createGroupConfig(group) {
|
|
324
263
|
if (!group) return null;
|
|
325
264
|
const defaultName = (ctx) => {
|
|
326
265
|
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
327
|
-
return
|
|
266
|
+
return camelCase(ctx.group);
|
|
328
267
|
};
|
|
329
268
|
return {
|
|
330
269
|
...group,
|
|
@@ -487,12 +426,12 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
|
|
|
487
426
|
if (hasCodec(node)) return true;
|
|
488
427
|
if (node.type === "ref") {
|
|
489
428
|
if (!node.ref) return false;
|
|
490
|
-
const refName =
|
|
429
|
+
const refName = extractRefName(node.ref);
|
|
491
430
|
if (refName) {
|
|
492
431
|
if (seen.has(refName)) return false;
|
|
493
432
|
seen.add(refName);
|
|
494
433
|
}
|
|
495
|
-
const resolved =
|
|
434
|
+
const resolved = syncSchemaRef(node);
|
|
496
435
|
if (resolved.type === "ref") return false;
|
|
497
436
|
return containsCodec(resolved, seen);
|
|
498
437
|
}
|
|
@@ -504,6 +443,13 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
|
|
|
504
443
|
return children.some((child) => containsCodec(child, seen));
|
|
505
444
|
}
|
|
506
445
|
/**
|
|
446
|
+
* Collects the names of `$ref` schemas that transitively contain a codec, so the generator can route
|
|
447
|
+
* them to their input (encode) variant.
|
|
448
|
+
*/
|
|
449
|
+
function collectCodecRefNames(node) {
|
|
450
|
+
return ast.collect(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? extractRefName(n.ref) ?? void 0 : void 0 });
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
507
453
|
* Collects all resolved schema names for an operation's parameters and responses
|
|
508
454
|
* into a single lookup object, useful for building imports and type references.
|
|
509
455
|
*/
|
|
@@ -628,7 +574,7 @@ function strictOneOfMember$1(member, node) {
|
|
|
628
574
|
if (node.type === "object" && node.additionalProperties === void 0) return `${member}.strict()`;
|
|
629
575
|
if (node.type === "ref") {
|
|
630
576
|
if (member.startsWith("z.lazy(")) return member;
|
|
631
|
-
const schema =
|
|
577
|
+
const schema = syncSchemaRef(node);
|
|
632
578
|
if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
|
|
633
579
|
}
|
|
634
580
|
return member;
|
|
@@ -713,24 +659,24 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
713
659
|
},
|
|
714
660
|
ref(node) {
|
|
715
661
|
if (!node.name) return null;
|
|
716
|
-
const refName = node.ref ?
|
|
662
|
+
const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
|
|
717
663
|
const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
|
|
718
664
|
const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.resolveInputSchemaName(refName) ?? refName : this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
719
665
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
720
666
|
return resolvedName;
|
|
721
667
|
},
|
|
722
668
|
object(node) {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
const isNullable = meta.nullable;
|
|
727
|
-
const isOptional = schema.optional;
|
|
728
|
-
const isNullish = schema.nullish;
|
|
729
|
-
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
669
|
+
const isCyclic = (schema) => this.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
670
|
+
const objectBase = `z.object(${buildObject(mapSchemaProperties(node, (schema) => {
|
|
671
|
+
const hasSelfRef = isCyclic(schema);
|
|
730
672
|
const savedCyclicSchemas = this.options.cyclicSchemas;
|
|
731
673
|
if (hasSelfRef) this.options.cyclicSchemas = void 0;
|
|
732
|
-
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: "unknown" }));
|
|
674
|
+
const baseOutput = this.transform(schema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
|
|
733
675
|
if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas;
|
|
676
|
+
return baseOutput;
|
|
677
|
+
}).map(({ name: propName, property, output: baseOutput }) => {
|
|
678
|
+
const { schema } = property;
|
|
679
|
+
const meta = syncSchemaRef(schema);
|
|
734
680
|
const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({
|
|
735
681
|
output: baseOutput,
|
|
736
682
|
schema
|
|
@@ -738,42 +684,41 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
738
684
|
const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
|
|
739
685
|
const value = applyModifiers({
|
|
740
686
|
value: wrappedOutput,
|
|
741
|
-
nullable:
|
|
742
|
-
optional:
|
|
743
|
-
nullish:
|
|
687
|
+
nullable: meta.nullable,
|
|
688
|
+
optional: schema.optional || property.required === false,
|
|
689
|
+
nullish: schema.nullish,
|
|
744
690
|
defaultValue: meta.default,
|
|
745
691
|
description: descriptionToApply
|
|
746
692
|
});
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
693
|
+
return isCyclic(schema) ? lazyGetter({
|
|
694
|
+
name: propName,
|
|
695
|
+
body: value
|
|
696
|
+
}) : `${objectKey(propName)}: ${value}`;
|
|
697
|
+
}))})`;
|
|
750
698
|
return (() => {
|
|
751
699
|
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
752
700
|
const catchallType = this.transform(node.additionalProperties);
|
|
753
701
|
return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
|
|
754
702
|
}
|
|
755
|
-
if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.createSchema({ type: "unknown" }))})`;
|
|
703
|
+
if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
|
|
756
704
|
if (node.additionalProperties === false) return `${objectBase}.strict()`;
|
|
757
705
|
return objectBase;
|
|
758
706
|
})();
|
|
759
707
|
},
|
|
760
708
|
array(node) {
|
|
761
|
-
const base = `z.array(${(node
|
|
709
|
+
const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthConstraints(node)}`;
|
|
762
710
|
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
763
711
|
},
|
|
764
712
|
tuple(node) {
|
|
765
|
-
return `z.tuple(
|
|
713
|
+
return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
|
|
766
714
|
},
|
|
767
715
|
union(node) {
|
|
768
716
|
const nodeMembers = node.members ?? [];
|
|
769
|
-
const members =
|
|
770
|
-
const member = this.transform(memberNode);
|
|
771
|
-
return member && node.strategy === "one" ? strictOneOfMember$1(member, memberNode) : member;
|
|
772
|
-
}).filter(Boolean);
|
|
717
|
+
const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema) : output).filter(Boolean);
|
|
773
718
|
if (members.length === 0) return "";
|
|
774
719
|
if (members.length === 1) return members[0];
|
|
775
|
-
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)},
|
|
776
|
-
return `z.union(
|
|
720
|
+
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
|
|
721
|
+
return `z.union(${buildList(members)})`;
|
|
777
722
|
},
|
|
778
723
|
intersection(node) {
|
|
779
724
|
const members = node.members ?? [];
|
|
@@ -795,7 +740,7 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
795
740
|
const { keysToOmit } = this.options;
|
|
796
741
|
const transformed = this.transform(node);
|
|
797
742
|
if (!transformed) return null;
|
|
798
|
-
const meta =
|
|
743
|
+
const meta = syncSchemaRef(node);
|
|
799
744
|
return applyModifiers({
|
|
800
745
|
value: (() => {
|
|
801
746
|
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
@@ -892,54 +837,53 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
892
837
|
},
|
|
893
838
|
ref(node) {
|
|
894
839
|
if (!node.name) return null;
|
|
895
|
-
const refName = node.ref ?
|
|
840
|
+
const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
|
|
896
841
|
const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
897
842
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
898
843
|
return resolvedName;
|
|
899
844
|
},
|
|
900
845
|
object(node) {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
const isNullable = meta.nullable;
|
|
905
|
-
const isOptional = schema.optional;
|
|
906
|
-
const isNullish = schema.nullish;
|
|
907
|
-
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
846
|
+
const isCyclic = (schema) => this.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
847
|
+
return `z.object(${buildObject(mapSchemaProperties(node, (schema) => {
|
|
848
|
+
const hasSelfRef = isCyclic(schema);
|
|
908
849
|
const savedCyclicSchemas = this.options.cyclicSchemas;
|
|
909
850
|
if (hasSelfRef) this.options.cyclicSchemas = void 0;
|
|
910
|
-
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: "unknown" }));
|
|
851
|
+
const baseOutput = this.transform(schema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
|
|
911
852
|
if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas;
|
|
853
|
+
return baseOutput;
|
|
854
|
+
}).map(({ name: propName, property, output: baseOutput }) => {
|
|
855
|
+
const { schema } = property;
|
|
856
|
+
const meta = syncSchemaRef(schema);
|
|
912
857
|
const value = applyMiniModifiers({
|
|
913
858
|
value: this.options.wrapOutput ? this.options.wrapOutput({
|
|
914
859
|
output: baseOutput,
|
|
915
860
|
schema
|
|
916
861
|
}) || baseOutput : baseOutput,
|
|
917
|
-
nullable:
|
|
918
|
-
optional:
|
|
919
|
-
nullish:
|
|
862
|
+
nullable: meta.nullable,
|
|
863
|
+
optional: schema.optional || property.required === false,
|
|
864
|
+
nullish: schema.nullish,
|
|
920
865
|
defaultValue: meta.default
|
|
921
866
|
});
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
867
|
+
return isCyclic(schema) ? lazyGetter({
|
|
868
|
+
name: propName,
|
|
869
|
+
body: value
|
|
870
|
+
}) : `${objectKey(propName)}: ${value}`;
|
|
871
|
+
}))})`;
|
|
925
872
|
},
|
|
926
873
|
array(node) {
|
|
927
|
-
const base = `z.array(${(node
|
|
874
|
+
const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthChecksMini(node)}`;
|
|
928
875
|
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
929
876
|
},
|
|
930
877
|
tuple(node) {
|
|
931
|
-
return `z.tuple(
|
|
878
|
+
return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
|
|
932
879
|
},
|
|
933
880
|
union(node) {
|
|
934
881
|
const nodeMembers = node.members ?? [];
|
|
935
|
-
const members =
|
|
936
|
-
const member = this.transform(memberNode);
|
|
937
|
-
return member && node.strategy === "one" ? strictOneOfMember(member, memberNode) : member;
|
|
938
|
-
}).filter(Boolean);
|
|
882
|
+
const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
|
|
939
883
|
if (members.length === 0) return "";
|
|
940
884
|
if (members.length === 1) return members[0];
|
|
941
|
-
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)},
|
|
942
|
-
return `z.union(
|
|
885
|
+
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
|
|
886
|
+
return `z.union(${buildList(members)})`;
|
|
943
887
|
},
|
|
944
888
|
intersection(node) {
|
|
945
889
|
const members = node.members ?? [];
|
|
@@ -961,7 +905,7 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
961
905
|
const { keysToOmit } = this.options;
|
|
962
906
|
const transformed = this.transform(node);
|
|
963
907
|
if (!transformed) return null;
|
|
964
|
-
const meta =
|
|
908
|
+
const meta = syncSchemaRef(node);
|
|
965
909
|
return applyMiniModifiers({
|
|
966
910
|
value: (() => {
|
|
967
911
|
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
@@ -1037,17 +981,16 @@ function getMiniPrinter(resolver, params) {
|
|
|
1037
981
|
*/
|
|
1038
982
|
const zodGenerator = defineGenerator({
|
|
1039
983
|
name: "zod",
|
|
1040
|
-
renderer:
|
|
984
|
+
renderer: jsxRenderer,
|
|
1041
985
|
schema(node, ctx) {
|
|
1042
986
|
const { adapter, config, resolver, root } = ctx;
|
|
1043
987
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options;
|
|
1044
988
|
const dateType = adapter.options.dateType;
|
|
1045
989
|
if (!node.name) return;
|
|
1046
|
-
const mode = ctx.getMode(output);
|
|
1047
990
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
1048
991
|
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1049
992
|
const hasCodec = !mini && containsCodec(node);
|
|
1050
|
-
const codecRefNames = new Set(hasCodec ?
|
|
993
|
+
const codecRefNames = new Set(hasCodec ? collectCodecRefNames(node) : []);
|
|
1051
994
|
const importEntries = adapter.getImports(node, (schemaName) => ({
|
|
1052
995
|
name: resolver.resolveSchemaName(schemaName),
|
|
1053
996
|
path: resolver.resolveFile({
|
|
@@ -1060,7 +1003,7 @@ const zodGenerator = defineGenerator({
|
|
|
1060
1003
|
}).path
|
|
1061
1004
|
}));
|
|
1062
1005
|
const inputImportEntries = hasCodec ? [...codecRefNames].map((schemaName) => ({
|
|
1063
|
-
name: resolver.resolveInputSchemaName(schemaName),
|
|
1006
|
+
name: [resolver.resolveInputSchemaName(schemaName)],
|
|
1064
1007
|
path: resolver.resolveFile({
|
|
1065
1008
|
name: schemaName,
|
|
1066
1009
|
extname: ".ts"
|
|
@@ -1129,7 +1072,7 @@ const zodGenerator = defineGenerator({
|
|
|
1129
1072
|
path: importPath,
|
|
1130
1073
|
isNameSpace: isZodImport
|
|
1131
1074
|
}),
|
|
1132
|
-
|
|
1075
|
+
imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1133
1076
|
root: meta.file.path,
|
|
1134
1077
|
path: imp.path,
|
|
1135
1078
|
name: imp.name
|
|
@@ -1158,9 +1101,8 @@ const zodGenerator = defineGenerator({
|
|
|
1158
1101
|
const { adapter, config, resolver, root } = ctx;
|
|
1159
1102
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
|
|
1160
1103
|
const dateType = adapter.options.dateType;
|
|
1161
|
-
const mode = ctx.getMode(output);
|
|
1162
1104
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
1163
|
-
const params =
|
|
1105
|
+
const params = caseParams(node.parameters, paramsCasing);
|
|
1164
1106
|
const meta = { file: resolver.resolveFile({
|
|
1165
1107
|
name: node.operationId,
|
|
1166
1108
|
extname: ".ts",
|
|
@@ -1175,7 +1117,7 @@ const zodGenerator = defineGenerator({
|
|
|
1175
1117
|
function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
|
|
1176
1118
|
if (!schema) return null;
|
|
1177
1119
|
const inferTypeName = inferred ? resolver.resolveTypeName(name) : null;
|
|
1178
|
-
const codecRefNames = direction === "input" && !mini ? new Set(
|
|
1120
|
+
const codecRefNames = direction === "input" && !mini ? new Set(collectCodecRefNames(schema)) : null;
|
|
1179
1121
|
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
1180
1122
|
name: codecRefNames?.has(schemaName) ? resolver.resolveInputSchemaName(schemaName) : resolver.resolveSchemaName(schemaName),
|
|
1181
1123
|
path: resolver.resolveFile({
|
|
@@ -1217,7 +1159,7 @@ const zodGenerator = defineGenerator({
|
|
|
1217
1159
|
cyclicSchemas,
|
|
1218
1160
|
nodes: printer?.nodes
|
|
1219
1161
|
})[direction];
|
|
1220
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1162
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1221
1163
|
root: meta.file.path,
|
|
1222
1164
|
path: imp.path,
|
|
1223
1165
|
name: imp.name
|
|
@@ -1234,9 +1176,9 @@ const zodGenerator = defineGenerator({
|
|
|
1234
1176
|
}
|
|
1235
1177
|
function buildContentTypeVariants(entries, baseName, decorate, direction) {
|
|
1236
1178
|
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1237
|
-
const unionSchema = ast.createSchema({
|
|
1179
|
+
const unionSchema = ast.factory.createSchema({
|
|
1238
1180
|
type: "union",
|
|
1239
|
-
members: variants.map((variant) => ast.createSchema({
|
|
1181
|
+
members: variants.map((variant) => ast.factory.createSchema({
|
|
1240
1182
|
type: "ref",
|
|
1241
1183
|
name: variant.name
|
|
1242
1184
|
}))
|
|
@@ -1274,12 +1216,12 @@ const zodGenerator = defineGenerator({
|
|
|
1274
1216
|
name: resolver.resolveSchemaName(schemaName),
|
|
1275
1217
|
path: ""
|
|
1276
1218
|
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(responseUnionName)) return null;
|
|
1277
|
-
const members = responsesWithSchema.map((res) => ast.createSchema({
|
|
1219
|
+
const members = responsesWithSchema.map((res) => ast.factory.createSchema({
|
|
1278
1220
|
type: "ref",
|
|
1279
1221
|
name: resolver.resolveResponseStatusName(node, res.statusCode)
|
|
1280
1222
|
}));
|
|
1281
1223
|
return renderSchemaEntry({
|
|
1282
|
-
schema: members.length === 1 ? members[0] : ast.createSchema({
|
|
1224
|
+
schema: members.length === 1 ? members[0] : ast.factory.createSchema({
|
|
1283
1225
|
type: "union",
|
|
1284
1226
|
members
|
|
1285
1227
|
}),
|
|
@@ -1357,7 +1299,7 @@ const zodGenerator = defineGenerator({
|
|
|
1357
1299
|
return {
|
|
1358
1300
|
node,
|
|
1359
1301
|
data: buildSchemaNames(node, {
|
|
1360
|
-
params:
|
|
1302
|
+
params: caseParams(node.parameters, paramsCasing),
|
|
1361
1303
|
resolver
|
|
1362
1304
|
})
|
|
1363
1305
|
};
|
|
@@ -1425,14 +1367,16 @@ const zodGenerator = defineGenerator({
|
|
|
1425
1367
|
/**
|
|
1426
1368
|
* Default resolver used by `@kubb/plugin-zod`. Decides the names and file
|
|
1427
1369
|
* paths for every generated Zod schema. Schemas use camelCase with a
|
|
1428
|
-
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
|
|
1370
|
+
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
|
|
1371
|
+
* with a `SchemaType` suffix (`PetSchemaType`), so the value and the type
|
|
1372
|
+
* never share an identifier even when the schema name is all-uppercase.
|
|
1429
1373
|
*
|
|
1430
1374
|
* @example Resolve schema and type names
|
|
1431
1375
|
* ```ts
|
|
1432
1376
|
* import { resolverZod } from '@kubb/plugin-zod'
|
|
1433
1377
|
*
|
|
1434
1378
|
* resolverZod.default('list pets', 'function') // 'listPetsSchema'
|
|
1435
|
-
* resolverZod.resolveSchemaTypeName('pet') // '
|
|
1379
|
+
* resolverZod.resolveSchemaTypeName('pet') // 'PetSchemaType'
|
|
1436
1380
|
* ```
|
|
1437
1381
|
*/
|
|
1438
1382
|
const resolverZod = defineResolver(() => {
|
|
@@ -1440,17 +1384,14 @@ const resolverZod = defineResolver(() => {
|
|
|
1440
1384
|
name: "default",
|
|
1441
1385
|
pluginName: "plugin-zod",
|
|
1442
1386
|
default(name, type) {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
suffix: type ? "schema" : void 0
|
|
1446
|
-
});
|
|
1447
|
-
return type === "file" ? resolved : ensureValidVarName(resolved);
|
|
1387
|
+
if (type === "file") return toFilePath(name, (part) => camelCase(part, { suffix: "schema" }));
|
|
1388
|
+
return ensureValidVarName(camelCase(name, { suffix: type ? "schema" : void 0 }));
|
|
1448
1389
|
},
|
|
1449
1390
|
resolveSchemaName(name) {
|
|
1450
1391
|
return ensureValidVarName(camelCase(name, { suffix: "schema" }));
|
|
1451
1392
|
},
|
|
1452
1393
|
resolveSchemaTypeName(name) {
|
|
1453
|
-
return ensureValidVarName(pascalCase(name, { suffix: "schema" }));
|
|
1394
|
+
return ensureValidVarName(pascalCase(name, { suffix: "schema type" }));
|
|
1454
1395
|
},
|
|
1455
1396
|
resolveInputSchemaName(name) {
|
|
1456
1397
|
return this.resolveSchemaName(`${name} input`);
|
|
@@ -1459,7 +1400,7 @@ const resolverZod = defineResolver(() => {
|
|
|
1459
1400
|
return this.resolveSchemaTypeName(`${name} input`);
|
|
1460
1401
|
},
|
|
1461
1402
|
resolveTypeName(name) {
|
|
1462
|
-
return ensureValidVarName(pascalCase(name));
|
|
1403
|
+
return ensureValidVarName(pascalCase(name, { suffix: "type" }));
|
|
1463
1404
|
},
|
|
1464
1405
|
resolvePathName(name, type) {
|
|
1465
1406
|
return this.default(name, type);
|
|
@@ -1525,9 +1466,9 @@ const pluginZodName = "plugin-zod";
|
|
|
1525
1466
|
const pluginZod = definePlugin((options) => {
|
|
1526
1467
|
const { output = {
|
|
1527
1468
|
path: "zod",
|
|
1528
|
-
|
|
1529
|
-
}, group, exclude = [], include, override = [], typed = false, operations = false, mini = false, guidType = "uuid", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, wrapOutput = void 0, paramsCasing, printer, resolver: userResolver,
|
|
1530
|
-
const groupConfig = createGroupConfig(group
|
|
1469
|
+
barrel: { type: "named" }
|
|
1470
|
+
}, group, exclude = [], include, override = [], typed = false, operations = false, mini = false, guidType = "uuid", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, wrapOutput = void 0, paramsCasing, printer, resolver: userResolver, macros: userMacros, generators: userGenerators = [] } = options;
|
|
1471
|
+
const groupConfig = createGroupConfig(group);
|
|
1531
1472
|
return {
|
|
1532
1473
|
name: pluginZodName,
|
|
1533
1474
|
options,
|
|
@@ -1553,7 +1494,7 @@ const pluginZod = definePlugin((options) => {
|
|
|
1553
1494
|
...resolverZod,
|
|
1554
1495
|
...userResolver
|
|
1555
1496
|
} : resolverZod);
|
|
1556
|
-
if (
|
|
1497
|
+
if (userMacros?.length) ctx.setMacros(userMacros);
|
|
1557
1498
|
ctx.addGenerator(zodGenerator);
|
|
1558
1499
|
for (const gen of userGenerators) ctx.addGenerator(gen);
|
|
1559
1500
|
} }
|