@kubb/plugin-zod 5.0.0-beta.42 → 5.0.0-beta.56
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 +75 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +16 -18
- package/dist/index.js +69 -133
- package/dist/index.js.map +1 -1
- package/package.json +9 -16
- package/src/components/Operations.tsx +1 -1
- package/src/generators/zodGenerator.tsx +12 -11
- package/src/plugin.ts +2 -2
- package/src/printers/printerZod.ts +45 -47
- package/src/printers/printerZodMini.ts +39 -41
- package/src/resolvers/resolverZod.ts +9 -7
- package/src/types.ts +12 -16
- package/src/utils.ts +2 -2
- package/extension.yaml +0 -970
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __name } from "./chunk-C0LytTxp.js";
|
|
2
|
-
import { Exclude, Generator, Group, Include, Output, Override, PluginFactoryOptions, Resolver, ast } from "@kubb/core";
|
|
2
|
+
import { Exclude, Generator, Group, Include, Output, OutputOptions, Override, PluginFactoryOptions, Resolver, ast } from "@kubb/core";
|
|
3
3
|
import { AdapterOas } from "@kubb/adapter-oas";
|
|
4
4
|
|
|
5
5
|
//#region src/printers/printerZod.d.ts
|
|
@@ -171,7 +171,7 @@ type ResolverZod = Resolver & ast.OperationParamsResolver & {
|
|
|
171
171
|
* Resolves the schema type name (inferred type from schema).
|
|
172
172
|
*
|
|
173
173
|
* @example Schema type names
|
|
174
|
-
* `resolver.resolveSchemaTypeName('pet') // → '
|
|
174
|
+
* `resolver.resolveSchemaTypeName('pet') // → 'PetSchemaType'`
|
|
175
175
|
*/
|
|
176
176
|
resolveSchemaTypeName(this: ResolverZod, name: string): string;
|
|
177
177
|
/**
|
|
@@ -186,14 +186,14 @@ type ResolverZod = Resolver & ast.OperationParamsResolver & {
|
|
|
186
186
|
* Resolves the inferred type name for the request (input) direction variant.
|
|
187
187
|
*
|
|
188
188
|
* @example Input schema type names
|
|
189
|
-
* `resolver.resolveInputSchemaTypeName('order') // → '
|
|
189
|
+
* `resolver.resolveInputSchemaTypeName('order') // → 'OrderInputSchemaType'`
|
|
190
190
|
*/
|
|
191
191
|
resolveInputSchemaTypeName(this: ResolverZod, name: string): string;
|
|
192
192
|
/**
|
|
193
193
|
* Resolves the generated type name from the schema.
|
|
194
194
|
*
|
|
195
195
|
* @example Type names
|
|
196
|
-
* `resolver.resolveTypeName('
|
|
196
|
+
* `resolver.resolveTypeName('petSchema') // → 'PetSchemaType'`
|
|
197
197
|
*/
|
|
198
198
|
resolveTypeName(this: ResolverZod, name: string): string;
|
|
199
199
|
/**
|
|
@@ -243,17 +243,13 @@ type ResolverZod = Resolver & ast.OperationParamsResolver & {
|
|
|
243
243
|
*/
|
|
244
244
|
resolveHeaderParamsName(this: ResolverZod, node: ast.OperationNode, param: ast.ParameterNode): string;
|
|
245
245
|
};
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Split generated files into subfolders based on the operation's tag.
|
|
255
|
-
*/
|
|
256
|
-
group?: Group;
|
|
246
|
+
/**
|
|
247
|
+
* Where the generated Zod schemas are written and how they are exported, plus the optional
|
|
248
|
+
* `group` strategy. The `group` option organizes `output.mode: 'directory'` output into per-tag or per-path subdirectories.
|
|
249
|
+
*
|
|
250
|
+
* @default { path: 'zod', barrel: { type: 'named' } }
|
|
251
|
+
*/
|
|
252
|
+
type Options = OutputOptions & {
|
|
257
253
|
/**
|
|
258
254
|
* Skip operations matching at least one entry in the list.
|
|
259
255
|
*/
|
|
@@ -270,7 +266,7 @@ type Options = {
|
|
|
270
266
|
* Module specifier used in the `import { z } from '...'` statement.
|
|
271
267
|
* Use `'zod/mini'` for the tree-shakeable bundle.
|
|
272
268
|
*
|
|
273
|
-
* @default 'zod'
|
|
269
|
+
* @default mini ? 'zod/mini' : 'zod'
|
|
274
270
|
*/
|
|
275
271
|
importPath?: 'zod' | 'zod/mini' | (string & {});
|
|
276
272
|
/**
|
|
@@ -427,14 +423,16 @@ declare const pluginZod: (options?: Options | undefined) => import("@kubb/core")
|
|
|
427
423
|
/**
|
|
428
424
|
* Default resolver used by `@kubb/plugin-zod`. Decides the names and file
|
|
429
425
|
* paths for every generated Zod schema. Schemas use camelCase with a
|
|
430
|
-
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
|
|
426
|
+
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
|
|
427
|
+
* with a `SchemaType` suffix (`PetSchemaType`), so the value and the type
|
|
428
|
+
* never share an identifier even when the schema name is all-uppercase.
|
|
431
429
|
*
|
|
432
430
|
* @example Resolve schema and type names
|
|
433
431
|
* ```ts
|
|
434
432
|
* import { resolverZod } from '@kubb/plugin-zod'
|
|
435
433
|
*
|
|
436
434
|
* resolverZod.default('list pets', 'function') // 'listPetsSchema'
|
|
437
|
-
* resolverZod.resolveSchemaTypeName('pet') // '
|
|
435
|
+
* resolverZod.resolveSchemaTypeName('pet') // 'PetSchemaType'
|
|
438
436
|
* ```
|
|
439
437
|
*/
|
|
440
438
|
declare const resolverZod: ResolverZod;
|
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, extractRefName, objectKey, stringify, stringifyObject, 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,7 +426,7 @@ 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);
|
|
@@ -713,14 +652,14 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
713
652
|
},
|
|
714
653
|
ref(node) {
|
|
715
654
|
if (!node.name) return null;
|
|
716
|
-
const refName = node.ref ?
|
|
655
|
+
const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
|
|
717
656
|
const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
|
|
718
657
|
const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.resolveInputSchemaName(refName) ?? refName : this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
719
658
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
720
659
|
return resolvedName;
|
|
721
660
|
},
|
|
722
661
|
object(node) {
|
|
723
|
-
const objectBase = `z.object(
|
|
662
|
+
const objectBase = `z.object(${buildObject(node.properties.map((prop) => {
|
|
724
663
|
const { name: propName, schema } = prop;
|
|
725
664
|
const meta = ast.syncSchemaRef(schema);
|
|
726
665
|
const isNullable = meta.nullable;
|
|
@@ -744,9 +683,9 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
744
683
|
defaultValue: meta.default,
|
|
745
684
|
description: descriptionToApply
|
|
746
685
|
});
|
|
747
|
-
if (hasSelfRef) return `get
|
|
748
|
-
return
|
|
749
|
-
})
|
|
686
|
+
if (hasSelfRef) return `get ${objectKey(propName)}() { return ${value} }`;
|
|
687
|
+
return `${objectKey(propName)}: ${value}`;
|
|
688
|
+
}))})`;
|
|
750
689
|
return (() => {
|
|
751
690
|
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
752
691
|
const catchallType = this.transform(node.additionalProperties);
|
|
@@ -762,7 +701,7 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
762
701
|
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
763
702
|
},
|
|
764
703
|
tuple(node) {
|
|
765
|
-
return `z.tuple(
|
|
704
|
+
return `z.tuple(${buildList((node.items ?? []).map((item) => this.transform(item)).filter(Boolean))})`;
|
|
766
705
|
},
|
|
767
706
|
union(node) {
|
|
768
707
|
const nodeMembers = node.members ?? [];
|
|
@@ -772,8 +711,8 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
772
711
|
}).filter(Boolean);
|
|
773
712
|
if (members.length === 0) return "";
|
|
774
713
|
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(
|
|
714
|
+
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
|
|
715
|
+
return `z.union(${buildList(members)})`;
|
|
777
716
|
},
|
|
778
717
|
intersection(node) {
|
|
779
718
|
const members = node.members ?? [];
|
|
@@ -892,13 +831,13 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
892
831
|
},
|
|
893
832
|
ref(node) {
|
|
894
833
|
if (!node.name) return null;
|
|
895
|
-
const refName = node.ref ?
|
|
834
|
+
const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
|
|
896
835
|
const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
897
836
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
898
837
|
return resolvedName;
|
|
899
838
|
},
|
|
900
839
|
object(node) {
|
|
901
|
-
return `z.object(
|
|
840
|
+
return `z.object(${buildObject(node.properties.map((prop) => {
|
|
902
841
|
const { name: propName, schema } = prop;
|
|
903
842
|
const meta = ast.syncSchemaRef(schema);
|
|
904
843
|
const isNullable = meta.nullable;
|
|
@@ -919,16 +858,16 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
919
858
|
nullish: isNullish,
|
|
920
859
|
defaultValue: meta.default
|
|
921
860
|
});
|
|
922
|
-
if (hasSelfRef) return `get
|
|
923
|
-
return
|
|
924
|
-
})
|
|
861
|
+
if (hasSelfRef) return `get ${objectKey(propName)}() { return ${value} }`;
|
|
862
|
+
return `${objectKey(propName)}: ${value}`;
|
|
863
|
+
}))})`;
|
|
925
864
|
},
|
|
926
865
|
array(node) {
|
|
927
866
|
const base = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(ast.createSchema({ type: "unknown" }))})${lengthChecksMini(node)}`;
|
|
928
867
|
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
929
868
|
},
|
|
930
869
|
tuple(node) {
|
|
931
|
-
return `z.tuple(
|
|
870
|
+
return `z.tuple(${buildList((node.items ?? []).map((item) => this.transform(item)).filter(Boolean))})`;
|
|
932
871
|
},
|
|
933
872
|
union(node) {
|
|
934
873
|
const nodeMembers = node.members ?? [];
|
|
@@ -938,8 +877,8 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
938
877
|
}).filter(Boolean);
|
|
939
878
|
if (members.length === 0) return "";
|
|
940
879
|
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(
|
|
880
|
+
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
|
|
881
|
+
return `z.union(${buildList(members)})`;
|
|
943
882
|
},
|
|
944
883
|
intersection(node) {
|
|
945
884
|
const members = node.members ?? [];
|
|
@@ -1037,17 +976,16 @@ function getMiniPrinter(resolver, params) {
|
|
|
1037
976
|
*/
|
|
1038
977
|
const zodGenerator = defineGenerator({
|
|
1039
978
|
name: "zod",
|
|
1040
|
-
renderer:
|
|
979
|
+
renderer: jsxRenderer,
|
|
1041
980
|
schema(node, ctx) {
|
|
1042
981
|
const { adapter, config, resolver, root } = ctx;
|
|
1043
982
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options;
|
|
1044
983
|
const dateType = adapter.options.dateType;
|
|
1045
984
|
if (!node.name) return;
|
|
1046
|
-
const mode = ctx.getMode(output);
|
|
1047
985
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
1048
986
|
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1049
987
|
const hasCodec = !mini && containsCodec(node);
|
|
1050
|
-
const codecRefNames = new Set(hasCodec ? ast.collect(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ?
|
|
988
|
+
const codecRefNames = new Set(hasCodec ? ast.collect(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? extractRefName(n.ref) ?? void 0 : void 0 }) : []);
|
|
1051
989
|
const importEntries = adapter.getImports(node, (schemaName) => ({
|
|
1052
990
|
name: resolver.resolveSchemaName(schemaName),
|
|
1053
991
|
path: resolver.resolveFile({
|
|
@@ -1060,7 +998,7 @@ const zodGenerator = defineGenerator({
|
|
|
1060
998
|
}).path
|
|
1061
999
|
}));
|
|
1062
1000
|
const inputImportEntries = hasCodec ? [...codecRefNames].map((schemaName) => ({
|
|
1063
|
-
name: resolver.resolveInputSchemaName(schemaName),
|
|
1001
|
+
name: [resolver.resolveInputSchemaName(schemaName)],
|
|
1064
1002
|
path: resolver.resolveFile({
|
|
1065
1003
|
name: schemaName,
|
|
1066
1004
|
extname: ".ts"
|
|
@@ -1129,7 +1067,7 @@ const zodGenerator = defineGenerator({
|
|
|
1129
1067
|
path: importPath,
|
|
1130
1068
|
isNameSpace: isZodImport
|
|
1131
1069
|
}),
|
|
1132
|
-
|
|
1070
|
+
imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1133
1071
|
root: meta.file.path,
|
|
1134
1072
|
path: imp.path,
|
|
1135
1073
|
name: imp.name
|
|
@@ -1158,7 +1096,6 @@ const zodGenerator = defineGenerator({
|
|
|
1158
1096
|
const { adapter, config, resolver, root } = ctx;
|
|
1159
1097
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
|
|
1160
1098
|
const dateType = adapter.options.dateType;
|
|
1161
|
-
const mode = ctx.getMode(output);
|
|
1162
1099
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
1163
1100
|
const params = ast.caseParams(node.parameters, paramsCasing);
|
|
1164
1101
|
const meta = { file: resolver.resolveFile({
|
|
@@ -1175,7 +1112,7 @@ const zodGenerator = defineGenerator({
|
|
|
1175
1112
|
function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
|
|
1176
1113
|
if (!schema) return null;
|
|
1177
1114
|
const inferTypeName = inferred ? resolver.resolveTypeName(name) : null;
|
|
1178
|
-
const codecRefNames = direction === "input" && !mini ? new Set(ast.collect(schema, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ?
|
|
1115
|
+
const codecRefNames = direction === "input" && !mini ? new Set(ast.collect(schema, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? extractRefName(n.ref) ?? void 0 : void 0 })) : null;
|
|
1179
1116
|
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
1180
1117
|
name: codecRefNames?.has(schemaName) ? resolver.resolveInputSchemaName(schemaName) : resolver.resolveSchemaName(schemaName),
|
|
1181
1118
|
path: resolver.resolveFile({
|
|
@@ -1217,7 +1154,7 @@ const zodGenerator = defineGenerator({
|
|
|
1217
1154
|
cyclicSchemas,
|
|
1218
1155
|
nodes: printer?.nodes
|
|
1219
1156
|
})[direction];
|
|
1220
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1157
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1221
1158
|
root: meta.file.path,
|
|
1222
1159
|
path: imp.path,
|
|
1223
1160
|
name: imp.name
|
|
@@ -1425,14 +1362,16 @@ const zodGenerator = defineGenerator({
|
|
|
1425
1362
|
/**
|
|
1426
1363
|
* Default resolver used by `@kubb/plugin-zod`. Decides the names and file
|
|
1427
1364
|
* paths for every generated Zod schema. Schemas use camelCase with a
|
|
1428
|
-
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
|
|
1365
|
+
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
|
|
1366
|
+
* with a `SchemaType` suffix (`PetSchemaType`), so the value and the type
|
|
1367
|
+
* never share an identifier even when the schema name is all-uppercase.
|
|
1429
1368
|
*
|
|
1430
1369
|
* @example Resolve schema and type names
|
|
1431
1370
|
* ```ts
|
|
1432
1371
|
* import { resolverZod } from '@kubb/plugin-zod'
|
|
1433
1372
|
*
|
|
1434
1373
|
* resolverZod.default('list pets', 'function') // 'listPetsSchema'
|
|
1435
|
-
* resolverZod.resolveSchemaTypeName('pet') // '
|
|
1374
|
+
* resolverZod.resolveSchemaTypeName('pet') // 'PetSchemaType'
|
|
1436
1375
|
* ```
|
|
1437
1376
|
*/
|
|
1438
1377
|
const resolverZod = defineResolver(() => {
|
|
@@ -1440,17 +1379,14 @@ const resolverZod = defineResolver(() => {
|
|
|
1440
1379
|
name: "default",
|
|
1441
1380
|
pluginName: "plugin-zod",
|
|
1442
1381
|
default(name, type) {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
suffix: type ? "schema" : void 0
|
|
1446
|
-
});
|
|
1447
|
-
return type === "file" ? resolved : ensureValidVarName(resolved);
|
|
1382
|
+
if (type === "file") return toFilePath(name, (part) => camelCase(part, { suffix: "schema" }));
|
|
1383
|
+
return ensureValidVarName(camelCase(name, { suffix: type ? "schema" : void 0 }));
|
|
1448
1384
|
},
|
|
1449
1385
|
resolveSchemaName(name) {
|
|
1450
1386
|
return ensureValidVarName(camelCase(name, { suffix: "schema" }));
|
|
1451
1387
|
},
|
|
1452
1388
|
resolveSchemaTypeName(name) {
|
|
1453
|
-
return ensureValidVarName(pascalCase(name, { suffix: "schema" }));
|
|
1389
|
+
return ensureValidVarName(pascalCase(name, { suffix: "schema type" }));
|
|
1454
1390
|
},
|
|
1455
1391
|
resolveInputSchemaName(name) {
|
|
1456
1392
|
return this.resolveSchemaName(`${name} input`);
|
|
@@ -1459,7 +1395,7 @@ const resolverZod = defineResolver(() => {
|
|
|
1459
1395
|
return this.resolveSchemaTypeName(`${name} input`);
|
|
1460
1396
|
},
|
|
1461
1397
|
resolveTypeName(name) {
|
|
1462
|
-
return ensureValidVarName(pascalCase(name));
|
|
1398
|
+
return ensureValidVarName(pascalCase(name, { suffix: "type" }));
|
|
1463
1399
|
},
|
|
1464
1400
|
resolvePathName(name, type) {
|
|
1465
1401
|
return this.default(name, type);
|
|
@@ -1525,9 +1461,9 @@ const pluginZodName = "plugin-zod";
|
|
|
1525
1461
|
const pluginZod = definePlugin((options) => {
|
|
1526
1462
|
const { output = {
|
|
1527
1463
|
path: "zod",
|
|
1528
|
-
|
|
1464
|
+
barrel: { type: "named" }
|
|
1529
1465
|
}, 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, transformer: userTransformer, generators: userGenerators = [] } = options;
|
|
1530
|
-
const groupConfig = createGroupConfig(group
|
|
1466
|
+
const groupConfig = createGroupConfig(group);
|
|
1531
1467
|
return {
|
|
1532
1468
|
name: pluginZodName,
|
|
1533
1469
|
options,
|