@kubb/plugin-zod 5.0.0-alpha.9 → 5.0.0-beta.4
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/LICENSE +17 -10
- package/README.md +1 -3
- package/dist/index.cjs +1061 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +369 -4
- package/dist/index.js +1053 -104
- package/dist/index.js.map +1 -1
- package/extension.yaml +502 -0
- package/package.json +44 -70
- package/src/components/Operations.tsx +25 -18
- package/src/components/Zod.tsx +21 -121
- package/src/constants.ts +5 -0
- package/src/generators/zodGenerator.tsx +174 -160
- package/src/index.ts +11 -2
- package/src/plugin.ts +67 -156
- package/src/printers/printerZod.ts +339 -0
- package/src/printers/printerZodMini.ts +295 -0
- package/src/resolvers/resolverZod.ts +57 -0
- package/src/types.ts +130 -115
- package/src/utils.ts +222 -0
- package/dist/components-B7zUFnAm.cjs +0 -890
- package/dist/components-B7zUFnAm.cjs.map +0 -1
- package/dist/components-eECfXVou.js +0 -842
- package/dist/components-eECfXVou.js.map +0 -1
- package/dist/components.cjs +0 -4
- package/dist/components.d.ts +0 -56
- package/dist/components.js +0 -2
- package/dist/generators-BjPDdJUz.cjs +0 -301
- package/dist/generators-BjPDdJUz.cjs.map +0 -1
- package/dist/generators-lTWPS6oN.js +0 -290
- package/dist/generators-lTWPS6oN.js.map +0 -1
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -508
- package/dist/generators.js +0 -2
- package/dist/templates/ToZod.source.cjs +0 -7
- package/dist/templates/ToZod.source.cjs.map +0 -1
- package/dist/templates/ToZod.source.d.ts +0 -7
- package/dist/templates/ToZod.source.js +0 -6
- package/dist/templates/ToZod.source.js.map +0 -1
- package/dist/types-CoCoOc2u.d.ts +0 -172
- package/src/components/index.ts +0 -2
- package/src/generators/index.ts +0 -2
- package/src/generators/operationsGenerator.tsx +0 -50
- package/src/parser.ts +0 -909
- package/src/templates/ToZod.source.ts +0 -4
- package/templates/ToZod.ts +0 -61
package/dist/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { createPlugin, getBarrelFiles, getMode, satisfiesDependency } from "@kubb/core";
|
|
6
|
-
import { OperationGenerator, SchemaGenerator, pluginOasName } from "@kubb/plugin-oas";
|
|
7
|
-
import { pluginTsName } from "@kubb/plugin-ts";
|
|
2
|
+
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
3
|
+
import { Const, File, Type, jsxRenderer } from "@kubb/renderer-jsx";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
8
5
|
//#region ../../internals/utils/src/casing.ts
|
|
9
6
|
/**
|
|
10
7
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -24,9 +21,12 @@ function toCamelOrPascal(text, pascal) {
|
|
|
24
21
|
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
25
22
|
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
26
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
27
|
*/
|
|
28
28
|
function applyToFileParts(text, transformPart) {
|
|
29
|
-
const parts = text.split(
|
|
29
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
30
30
|
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
@@ -60,116 +60,1065 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
60
60
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
61
61
|
}
|
|
62
62
|
//#endregion
|
|
63
|
+
//#region ../../internals/utils/src/string.ts
|
|
64
|
+
/**
|
|
65
|
+
* Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
|
|
66
|
+
* Returns the string unchanged when no balanced quote pair is found.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* trimQuotes('"hello"') // 'hello'
|
|
70
|
+
* trimQuotes('hello') // 'hello'
|
|
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.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* stringify('hello') // '"hello"'
|
|
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.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* stringifyObject({ foo: 'bar', nested: { a: 1 } })
|
|
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.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* toRegExpString('^(?im)foo') // → 'new RegExp("foo", "im")'
|
|
116
|
+
* toRegExpString('^(?im)foo', null) // → '/foo/im'
|
|
117
|
+
*/
|
|
118
|
+
function toRegExpString(text, func = "RegExp") {
|
|
119
|
+
const raw = trimQuotes(text);
|
|
120
|
+
const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
|
|
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)}` : ""})`;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/components/Operations.tsx
|
|
130
|
+
function Operations({ name, operations }) {
|
|
131
|
+
const operationsJSON = operations.reduce((prev, acc) => {
|
|
132
|
+
prev[`"${acc.node.operationId}"`] = acc.data;
|
|
133
|
+
return prev;
|
|
134
|
+
}, {});
|
|
135
|
+
const pathsJSON = operations.reduce((prev, acc) => {
|
|
136
|
+
prev[`"${acc.node.path}"`] = {
|
|
137
|
+
...prev[`"${acc.node.path}"`] ?? {},
|
|
138
|
+
[acc.node.method]: `operations["${acc.node.operationId}"]`
|
|
139
|
+
};
|
|
140
|
+
return prev;
|
|
141
|
+
}, {});
|
|
142
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
143
|
+
/* @__PURE__ */ jsx(File.Source, {
|
|
144
|
+
name: "OperationSchema",
|
|
145
|
+
isExportable: true,
|
|
146
|
+
isIndexable: true,
|
|
147
|
+
children: /* @__PURE__ */ jsx(Type, {
|
|
148
|
+
name: "OperationSchema",
|
|
149
|
+
export: true,
|
|
150
|
+
children: `{
|
|
151
|
+
readonly request: z.ZodTypeAny | undefined;
|
|
152
|
+
readonly parameters: {
|
|
153
|
+
readonly path: z.ZodTypeAny | undefined;
|
|
154
|
+
readonly query: z.ZodTypeAny | undefined;
|
|
155
|
+
readonly header: z.ZodTypeAny | undefined;
|
|
156
|
+
};
|
|
157
|
+
readonly responses: {
|
|
158
|
+
readonly [status: number]: z.ZodTypeAny;
|
|
159
|
+
readonly default: z.ZodTypeAny;
|
|
160
|
+
};
|
|
161
|
+
readonly errors: {
|
|
162
|
+
readonly [status: number]: z.ZodTypeAny;
|
|
163
|
+
};
|
|
164
|
+
}`
|
|
165
|
+
})
|
|
166
|
+
}),
|
|
167
|
+
/* @__PURE__ */ jsx(File.Source, {
|
|
168
|
+
name: "OperationsMap",
|
|
169
|
+
isExportable: true,
|
|
170
|
+
isIndexable: true,
|
|
171
|
+
children: /* @__PURE__ */ jsx(Type, {
|
|
172
|
+
name: "OperationsMap",
|
|
173
|
+
export: true,
|
|
174
|
+
children: "Record<string, OperationSchema>"
|
|
175
|
+
})
|
|
176
|
+
}),
|
|
177
|
+
/* @__PURE__ */ jsx(File.Source, {
|
|
178
|
+
name,
|
|
179
|
+
isExportable: true,
|
|
180
|
+
isIndexable: true,
|
|
181
|
+
children: /* @__PURE__ */ jsx(Const, {
|
|
182
|
+
export: true,
|
|
183
|
+
name,
|
|
184
|
+
asConst: true,
|
|
185
|
+
children: `{${stringifyObject(operationsJSON)}}`
|
|
186
|
+
})
|
|
187
|
+
}),
|
|
188
|
+
/* @__PURE__ */ jsx(File.Source, {
|
|
189
|
+
name: "paths",
|
|
190
|
+
isExportable: true,
|
|
191
|
+
isIndexable: true,
|
|
192
|
+
children: /* @__PURE__ */ jsx(Const, {
|
|
193
|
+
export: true,
|
|
194
|
+
name: "paths",
|
|
195
|
+
asConst: true,
|
|
196
|
+
children: `{${stringifyObject(pathsJSON)}}`
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
] });
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/components/Zod.tsx
|
|
203
|
+
function Zod({ name, node, printer, inferTypeName }) {
|
|
204
|
+
const output = printer.print(node);
|
|
205
|
+
if (!output) return;
|
|
206
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Source, {
|
|
207
|
+
name,
|
|
208
|
+
isExportable: true,
|
|
209
|
+
isIndexable: true,
|
|
210
|
+
children: /* @__PURE__ */ jsx(Const, {
|
|
211
|
+
export: true,
|
|
212
|
+
name,
|
|
213
|
+
children: output
|
|
214
|
+
})
|
|
215
|
+
}), inferTypeName && /* @__PURE__ */ jsx(File.Source, {
|
|
216
|
+
name: inferTypeName,
|
|
217
|
+
isExportable: true,
|
|
218
|
+
isIndexable: true,
|
|
219
|
+
isTypeOnly: true,
|
|
220
|
+
children: /* @__PURE__ */ jsx(Type, {
|
|
221
|
+
export: true,
|
|
222
|
+
name: inferTypeName,
|
|
223
|
+
children: `z.infer<typeof ${name}>`
|
|
224
|
+
})
|
|
225
|
+
})] });
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/constants.ts
|
|
229
|
+
/**
|
|
230
|
+
* Import paths that use a namespace import (`import * as z from '...'`).
|
|
231
|
+
* All other import paths use a named import (`import { z } from '...'`).
|
|
232
|
+
*/
|
|
233
|
+
const ZOD_NAMESPACE_IMPORTS = new Set(["zod", "zod/mini"]);
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/utils.ts
|
|
236
|
+
/**
|
|
237
|
+
* Returns `true` when the given coercion option enables coercion for the specified type.
|
|
238
|
+
*/
|
|
239
|
+
function shouldCoerce(coercion, type) {
|
|
240
|
+
if (coercion === void 0 || coercion === false) return false;
|
|
241
|
+
if (coercion === true) return true;
|
|
242
|
+
return !!coercion[type];
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Collects all resolved schema names for an operation's parameters and responses
|
|
246
|
+
* into a single lookup object, useful for building imports and type references.
|
|
247
|
+
*/
|
|
248
|
+
function buildSchemaNames(node, { params, resolver }) {
|
|
249
|
+
const pathParam = params.find((p) => p.in === "path");
|
|
250
|
+
const queryParam = params.find((p) => p.in === "query");
|
|
251
|
+
const headerParam = params.find((p) => p.in === "header");
|
|
252
|
+
const responses = {};
|
|
253
|
+
const errors = {};
|
|
254
|
+
for (const res of node.responses) {
|
|
255
|
+
const name = resolver.resolveResponseStatusName(node, res.statusCode);
|
|
256
|
+
const statusNum = Number(res.statusCode);
|
|
257
|
+
if (!Number.isNaN(statusNum)) {
|
|
258
|
+
responses[statusNum] = name;
|
|
259
|
+
if (statusNum >= 400) errors[statusNum] = name;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
responses["default"] = resolver.resolveResponseName(node);
|
|
263
|
+
return {
|
|
264
|
+
request: node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0,
|
|
265
|
+
parameters: {
|
|
266
|
+
path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : void 0,
|
|
267
|
+
query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : void 0,
|
|
268
|
+
header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : void 0
|
|
269
|
+
},
|
|
270
|
+
responses,
|
|
271
|
+
errors
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Format a default value as a code-level literal.
|
|
276
|
+
* Objects become `{}`, primitives become their string representation, strings are quoted.
|
|
277
|
+
*/
|
|
278
|
+
function formatDefault(value) {
|
|
279
|
+
if (typeof value === "string") return stringify(value);
|
|
280
|
+
if (typeof value === "object" && value !== null) return "{}";
|
|
281
|
+
return String(value ?? "");
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Format a primitive enum/literal value.
|
|
285
|
+
* Strings are quoted; numbers and booleans are emitted raw.
|
|
286
|
+
*/
|
|
287
|
+
function formatLiteral(v) {
|
|
288
|
+
if (typeof v === "string") return stringify(v);
|
|
289
|
+
return String(v);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
|
|
293
|
+
* using the standard chainable Zod v4 API.
|
|
294
|
+
*/
|
|
295
|
+
function numberConstraints({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }) {
|
|
296
|
+
return [
|
|
297
|
+
min !== void 0 ? `.min(${min})` : "",
|
|
298
|
+
max !== void 0 ? `.max(${max})` : "",
|
|
299
|
+
exclusiveMinimum !== void 0 ? `.gt(${exclusiveMinimum})` : "",
|
|
300
|
+
exclusiveMaximum !== void 0 ? `.lt(${exclusiveMaximum})` : "",
|
|
301
|
+
multipleOf !== void 0 ? `.multipleOf(${multipleOf})` : ""
|
|
302
|
+
].join("");
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Build `.min()` / `.max()` / `.regex()` chains for strings/arrays
|
|
306
|
+
* using the standard chainable Zod v4 API.
|
|
307
|
+
*/
|
|
308
|
+
function lengthConstraints({ min, max, pattern }) {
|
|
309
|
+
return [
|
|
310
|
+
min !== void 0 ? `.min(${min})` : "",
|
|
311
|
+
max !== void 0 ? `.max(${max})` : "",
|
|
312
|
+
pattern !== void 0 ? `.regex(${toRegExpString(pattern, null)})` : ""
|
|
313
|
+
].join("");
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Build `.check(z.minimum(), z.maximum())` for `zod/mini` numeric constraints.
|
|
317
|
+
*/
|
|
318
|
+
function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }) {
|
|
319
|
+
const checks = [];
|
|
320
|
+
if (min !== void 0) checks.push(`z.minimum(${min})`);
|
|
321
|
+
if (max !== void 0) checks.push(`z.maximum(${max})`);
|
|
322
|
+
if (exclusiveMinimum !== void 0) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`);
|
|
323
|
+
if (exclusiveMaximum !== void 0) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`);
|
|
324
|
+
if (multipleOf !== void 0) checks.push(`z.multipleOf(${multipleOf})`);
|
|
325
|
+
return checks.length ? `.check(${checks.join(", ")})` : "";
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
|
|
329
|
+
*/
|
|
330
|
+
function lengthChecksMini({ min, max, pattern }) {
|
|
331
|
+
const checks = [];
|
|
332
|
+
if (min !== void 0) checks.push(`z.minLength(${min})`);
|
|
333
|
+
if (max !== void 0) checks.push(`z.maxLength(${max})`);
|
|
334
|
+
if (pattern !== void 0) checks.push(`z.regex(${toRegExpString(pattern, null)})`);
|
|
335
|
+
return checks.length ? `.check(${checks.join(", ")})` : "";
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Apply nullable / optional / nullish modifiers and an optional `.describe()` call
|
|
339
|
+
* to a schema value string using the chainable Zod v4 API.
|
|
340
|
+
*/
|
|
341
|
+
function applyModifiers({ value, nullable, optional, nullish, defaultValue, description }) {
|
|
342
|
+
let result = value;
|
|
343
|
+
if (nullish || nullable && optional) result = `${result}.nullish()`;
|
|
344
|
+
else if (optional) result = `${result}.optional()`;
|
|
345
|
+
else if (nullable) result = `${result}.nullable()`;
|
|
346
|
+
if (defaultValue !== void 0) result = `${result}.default(${formatDefault(defaultValue)})`;
|
|
347
|
+
if (description) result = `${result}.describe(${stringify(description)})`;
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
|
|
352
|
+
* (`z.nullable()`, `z.optional()`, `z.nullish()`).
|
|
353
|
+
*/
|
|
354
|
+
function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }) {
|
|
355
|
+
let result = value;
|
|
356
|
+
if (nullish) result = `z.nullish(${result})`;
|
|
357
|
+
else {
|
|
358
|
+
if (nullable) result = `z.nullable(${result})`;
|
|
359
|
+
if (optional) result = `z.optional(${result})`;
|
|
360
|
+
}
|
|
361
|
+
if (defaultValue !== void 0) result = `z._default(${result}, ${formatDefault(defaultValue)})`;
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/printers/printerZod.ts
|
|
366
|
+
/**
|
|
367
|
+
* Zod v4 printer built with `definePrinter`.
|
|
368
|
+
*
|
|
369
|
+
* Converts a `SchemaNode` AST into a Zod v4 code string using the chainable API
|
|
370
|
+
* (`.optional()`, `.nullable()`, `.omit()`, etc.). For improved tree-shaking, see {@link printerZodMini}.
|
|
371
|
+
*
|
|
372
|
+
* @example Chainable API
|
|
373
|
+
* ```ts
|
|
374
|
+
* const printer = printerZod({ coercion: false })
|
|
375
|
+
* const code = printer.print(stringNode) // "z.string()"
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
const printerZod = ast.definePrinter((options) => {
|
|
379
|
+
return {
|
|
380
|
+
name: "zod",
|
|
381
|
+
options,
|
|
382
|
+
nodes: {
|
|
383
|
+
any: () => "z.any()",
|
|
384
|
+
unknown: () => "z.unknown()",
|
|
385
|
+
void: () => "z.void()",
|
|
386
|
+
never: () => "z.never()",
|
|
387
|
+
boolean: () => "z.boolean()",
|
|
388
|
+
null: () => "z.null()",
|
|
389
|
+
string(node) {
|
|
390
|
+
return `${shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()"}${lengthConstraints(node)}`;
|
|
391
|
+
},
|
|
392
|
+
number(node) {
|
|
393
|
+
return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${numberConstraints(node)}`;
|
|
394
|
+
},
|
|
395
|
+
integer(node) {
|
|
396
|
+
return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number().int()" : "z.int()"}${numberConstraints(node)}`;
|
|
397
|
+
},
|
|
398
|
+
bigint() {
|
|
399
|
+
return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
|
|
400
|
+
},
|
|
401
|
+
date(node) {
|
|
402
|
+
if (node.representation === "string") return "z.iso.date()";
|
|
403
|
+
return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
|
|
404
|
+
},
|
|
405
|
+
datetime(node) {
|
|
406
|
+
const offset = node.offset || this.options.dateType === "stringOffset";
|
|
407
|
+
const local = node.local || this.options.dateType === "stringLocal";
|
|
408
|
+
if (offset) return "z.iso.datetime({ offset: true })";
|
|
409
|
+
if (local) return "z.iso.datetime({ local: true })";
|
|
410
|
+
return "z.iso.datetime()";
|
|
411
|
+
},
|
|
412
|
+
time(node) {
|
|
413
|
+
if (node.representation === "string") return "z.iso.time()";
|
|
414
|
+
return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
|
|
415
|
+
},
|
|
416
|
+
uuid(node) {
|
|
417
|
+
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints(node)}`;
|
|
418
|
+
},
|
|
419
|
+
email(node) {
|
|
420
|
+
return `z.email()${lengthConstraints(node)}`;
|
|
421
|
+
},
|
|
422
|
+
url(node) {
|
|
423
|
+
return `z.url()${lengthConstraints(node)}`;
|
|
424
|
+
},
|
|
425
|
+
ipv4: () => "z.ipv4()",
|
|
426
|
+
ipv6: () => "z.ipv6()",
|
|
427
|
+
blob: () => "z.instanceof(File)",
|
|
428
|
+
enum(node) {
|
|
429
|
+
const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
|
|
430
|
+
if (node.namedEnumValues?.length) {
|
|
431
|
+
const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`);
|
|
432
|
+
if (literals.length === 1) return literals[0];
|
|
433
|
+
return `z.union([${literals.join(", ")}])`;
|
|
434
|
+
}
|
|
435
|
+
return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
|
|
436
|
+
},
|
|
437
|
+
ref(node) {
|
|
438
|
+
if (!node.name) return void 0;
|
|
439
|
+
const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
|
|
440
|
+
const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
441
|
+
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
442
|
+
return resolvedName;
|
|
443
|
+
},
|
|
444
|
+
object(node) {
|
|
445
|
+
let result = `z.object({\n ${node.properties.map((prop) => {
|
|
446
|
+
const { name: propName, schema } = prop;
|
|
447
|
+
const meta = ast.syncSchemaRef(schema);
|
|
448
|
+
const isNullable = meta.nullable;
|
|
449
|
+
const isOptional = schema.optional;
|
|
450
|
+
const isNullish = schema.nullish;
|
|
451
|
+
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
452
|
+
if (hasSelfRef) this.options.cyclicSchemas = void 0;
|
|
453
|
+
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: "unknown" }));
|
|
454
|
+
if (hasSelfRef) this.options.cyclicSchemas = options.cyclicSchemas;
|
|
455
|
+
const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({
|
|
456
|
+
output: baseOutput,
|
|
457
|
+
schema
|
|
458
|
+
}) || baseOutput : baseOutput;
|
|
459
|
+
let descriptionToApply = meta.description;
|
|
460
|
+
if (schema.type !== "ref" && meta.type === "ref") descriptionToApply = void 0;
|
|
461
|
+
const value = applyModifiers({
|
|
462
|
+
value: wrappedOutput,
|
|
463
|
+
nullable: isNullable,
|
|
464
|
+
optional: isOptional,
|
|
465
|
+
nullish: isNullish,
|
|
466
|
+
defaultValue: meta.default,
|
|
467
|
+
description: descriptionToApply
|
|
468
|
+
});
|
|
469
|
+
if (hasSelfRef) return `get "${propName}"() { return ${value} }`;
|
|
470
|
+
return `"${propName}": ${value}`;
|
|
471
|
+
}).join(",\n ")}\n })`;
|
|
472
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
473
|
+
const catchallType = this.transform(node.additionalProperties);
|
|
474
|
+
if (catchallType) result += `.catchall(${catchallType})`;
|
|
475
|
+
} else if (node.additionalProperties === true) result += `.catchall(${this.transform(ast.createSchema({ type: "unknown" }))})`;
|
|
476
|
+
else if (node.additionalProperties === false) result += ".strict()";
|
|
477
|
+
return result;
|
|
478
|
+
},
|
|
479
|
+
array(node) {
|
|
480
|
+
let result = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(ast.createSchema({ type: "unknown" }))})${lengthConstraints(node)}`;
|
|
481
|
+
if (node.unique) result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`;
|
|
482
|
+
return result;
|
|
483
|
+
},
|
|
484
|
+
tuple(node) {
|
|
485
|
+
return `z.tuple([${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ")}])`;
|
|
486
|
+
},
|
|
487
|
+
union(node) {
|
|
488
|
+
const nodeMembers = node.members ?? [];
|
|
489
|
+
const members = nodeMembers.map((m) => this.transform(m)).filter(Boolean);
|
|
490
|
+
if (members.length === 0) return "";
|
|
491
|
+
if (members.length === 1) return members[0];
|
|
492
|
+
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(", ")}])`;
|
|
493
|
+
return `z.union([${members.join(", ")}])`;
|
|
494
|
+
},
|
|
495
|
+
intersection(node) {
|
|
496
|
+
const members = node.members ?? [];
|
|
497
|
+
if (members.length === 0) return "";
|
|
498
|
+
const [first, ...rest] = members;
|
|
499
|
+
if (!first) return "";
|
|
500
|
+
let base = this.transform(first);
|
|
501
|
+
if (!base) return "";
|
|
502
|
+
for (const member of rest) {
|
|
503
|
+
if (member.primitive === "string") {
|
|
504
|
+
const c = lengthConstraints(ast.narrowSchema(member, "string") ?? {});
|
|
505
|
+
if (c) {
|
|
506
|
+
base += c;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
} else if (member.primitive === "number" || member.primitive === "integer") {
|
|
510
|
+
const c = numberConstraints(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {});
|
|
511
|
+
if (c) {
|
|
512
|
+
base += c;
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
} else if (member.primitive === "array") {
|
|
516
|
+
const c = lengthConstraints(ast.narrowSchema(member, "array") ?? {});
|
|
517
|
+
if (c) {
|
|
518
|
+
base += c;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const transformed = this.transform(member);
|
|
523
|
+
if (transformed) base = `${base}.and(${transformed})`;
|
|
524
|
+
}
|
|
525
|
+
return base;
|
|
526
|
+
},
|
|
527
|
+
...options.nodes
|
|
528
|
+
},
|
|
529
|
+
print(node) {
|
|
530
|
+
const { keysToOmit } = this.options;
|
|
531
|
+
let base = this.transform(node);
|
|
532
|
+
if (!base) return null;
|
|
533
|
+
const meta = ast.syncSchemaRef(node);
|
|
534
|
+
if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
|
|
535
|
+
const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
536
|
+
if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
537
|
+
else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
538
|
+
}
|
|
539
|
+
return applyModifiers({
|
|
540
|
+
value: base,
|
|
541
|
+
nullable: meta.nullable,
|
|
542
|
+
optional: meta.optional,
|
|
543
|
+
nullish: meta.nullish,
|
|
544
|
+
defaultValue: meta.default,
|
|
545
|
+
description: meta.description
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/printers/printerZodMini.ts
|
|
552
|
+
/**
|
|
553
|
+
* Zod v4 **Mini** printer built with `definePrinter`.
|
|
554
|
+
*
|
|
555
|
+
* Converts a `SchemaNode` AST into a Zod v4 code string using the functional API
|
|
556
|
+
* (`z.optional(z.string())`) for improved tree-shaking. See {@link printerZod} for the chainable API.
|
|
557
|
+
*
|
|
558
|
+
* @example Functional Mini API
|
|
559
|
+
* ```ts
|
|
560
|
+
* const printer = printerZodMini({})
|
|
561
|
+
* const code = printer.print(optionalStringNode) // "z.optional(z.string())"
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
const printerZodMini = ast.definePrinter((options) => {
|
|
565
|
+
return {
|
|
566
|
+
name: "zod-mini",
|
|
567
|
+
options,
|
|
568
|
+
nodes: {
|
|
569
|
+
any: () => "z.any()",
|
|
570
|
+
unknown: () => "z.unknown()",
|
|
571
|
+
void: () => "z.void()",
|
|
572
|
+
never: () => "z.never()",
|
|
573
|
+
boolean: () => "z.boolean()",
|
|
574
|
+
null: () => "z.null()",
|
|
575
|
+
string(node) {
|
|
576
|
+
return `z.string()${lengthChecksMini(node)}`;
|
|
577
|
+
},
|
|
578
|
+
number(node) {
|
|
579
|
+
return `z.number()${numberChecksMini(node)}`;
|
|
580
|
+
},
|
|
581
|
+
integer(node) {
|
|
582
|
+
return `z.int()${numberChecksMini(node)}`;
|
|
583
|
+
},
|
|
584
|
+
bigint(node) {
|
|
585
|
+
return `z.bigint()${numberChecksMini(node)}`;
|
|
586
|
+
},
|
|
587
|
+
date(node) {
|
|
588
|
+
if (node.representation === "string") return "z.iso.date()";
|
|
589
|
+
return "z.date()";
|
|
590
|
+
},
|
|
591
|
+
datetime() {
|
|
592
|
+
return "z.string()";
|
|
593
|
+
},
|
|
594
|
+
time(node) {
|
|
595
|
+
if (node.representation === "string") return "z.iso.time()";
|
|
596
|
+
return "z.date()";
|
|
597
|
+
},
|
|
598
|
+
uuid(node) {
|
|
599
|
+
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthChecksMini(node)}`;
|
|
600
|
+
},
|
|
601
|
+
email(node) {
|
|
602
|
+
return `z.email()${lengthChecksMini(node)}`;
|
|
603
|
+
},
|
|
604
|
+
url(node) {
|
|
605
|
+
return `z.url()${lengthChecksMini(node)}`;
|
|
606
|
+
},
|
|
607
|
+
ipv4: () => "z.ipv4()",
|
|
608
|
+
ipv6: () => "z.ipv6()",
|
|
609
|
+
blob: () => "z.instanceof(File)",
|
|
610
|
+
enum(node) {
|
|
611
|
+
const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
|
|
612
|
+
if (node.namedEnumValues?.length) {
|
|
613
|
+
const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`);
|
|
614
|
+
if (literals.length === 1) return literals[0];
|
|
615
|
+
return `z.union([${literals.join(", ")}])`;
|
|
616
|
+
}
|
|
617
|
+
return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
|
|
618
|
+
},
|
|
619
|
+
ref(node) {
|
|
620
|
+
if (!node.name) return void 0;
|
|
621
|
+
const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
|
|
622
|
+
const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
623
|
+
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
624
|
+
return resolvedName;
|
|
625
|
+
},
|
|
626
|
+
object(node) {
|
|
627
|
+
return `z.object({\n ${node.properties.map((prop) => {
|
|
628
|
+
const { name: propName, schema } = prop;
|
|
629
|
+
const meta = ast.syncSchemaRef(schema);
|
|
630
|
+
const isNullable = meta.nullable;
|
|
631
|
+
const isOptional = schema.optional;
|
|
632
|
+
const isNullish = schema.nullish;
|
|
633
|
+
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
634
|
+
if (hasSelfRef) this.options.cyclicSchemas = void 0;
|
|
635
|
+
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: "unknown" }));
|
|
636
|
+
if (hasSelfRef) this.options.cyclicSchemas = options.cyclicSchemas;
|
|
637
|
+
const value = applyMiniModifiers({
|
|
638
|
+
value: this.options.wrapOutput ? this.options.wrapOutput({
|
|
639
|
+
output: baseOutput,
|
|
640
|
+
schema
|
|
641
|
+
}) || baseOutput : baseOutput,
|
|
642
|
+
nullable: isNullable,
|
|
643
|
+
optional: isOptional,
|
|
644
|
+
nullish: isNullish,
|
|
645
|
+
defaultValue: meta.default
|
|
646
|
+
});
|
|
647
|
+
if (hasSelfRef) return `get "${propName}"() { return ${value} }`;
|
|
648
|
+
return `"${propName}": ${value}`;
|
|
649
|
+
}).join(",\n ")}\n })`;
|
|
650
|
+
},
|
|
651
|
+
array(node) {
|
|
652
|
+
let result = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(ast.createSchema({ type: "unknown" }))})${lengthChecksMini(node)}`;
|
|
653
|
+
if (node.unique) result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`;
|
|
654
|
+
return result;
|
|
655
|
+
},
|
|
656
|
+
tuple(node) {
|
|
657
|
+
return `z.tuple([${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ")}])`;
|
|
658
|
+
},
|
|
659
|
+
union(node) {
|
|
660
|
+
const nodeMembers = node.members ?? [];
|
|
661
|
+
const members = nodeMembers.map((m) => this.transform(m)).filter(Boolean);
|
|
662
|
+
if (members.length === 0) return "";
|
|
663
|
+
if (members.length === 1) return members[0];
|
|
664
|
+
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(", ")}])`;
|
|
665
|
+
return `z.union([${members.join(", ")}])`;
|
|
666
|
+
},
|
|
667
|
+
intersection(node) {
|
|
668
|
+
const members = node.members ?? [];
|
|
669
|
+
if (members.length === 0) return "";
|
|
670
|
+
const [first, ...rest] = members;
|
|
671
|
+
if (!first) return "";
|
|
672
|
+
let base = this.transform(first);
|
|
673
|
+
if (!base) return "";
|
|
674
|
+
for (const member of rest) {
|
|
675
|
+
if (member.primitive === "string") {
|
|
676
|
+
const c = lengthChecksMini(ast.narrowSchema(member, "string") ?? {});
|
|
677
|
+
if (c) {
|
|
678
|
+
base += c;
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
} else if (member.primitive === "number" || member.primitive === "integer") {
|
|
682
|
+
const c = numberChecksMini(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {});
|
|
683
|
+
if (c) {
|
|
684
|
+
base += c;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
} else if (member.primitive === "array") {
|
|
688
|
+
const c = lengthChecksMini(ast.narrowSchema(member, "array") ?? {});
|
|
689
|
+
if (c) {
|
|
690
|
+
base += c;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const transformed = this.transform(member);
|
|
695
|
+
if (transformed) base = `z.intersection(${base}, ${transformed})`;
|
|
696
|
+
}
|
|
697
|
+
return base;
|
|
698
|
+
},
|
|
699
|
+
...options.nodes
|
|
700
|
+
},
|
|
701
|
+
print(node) {
|
|
702
|
+
const { keysToOmit } = this.options;
|
|
703
|
+
let base = this.transform(node);
|
|
704
|
+
if (!base) return null;
|
|
705
|
+
const meta = ast.syncSchemaRef(node);
|
|
706
|
+
if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
|
|
707
|
+
const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
708
|
+
if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
709
|
+
else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
710
|
+
}
|
|
711
|
+
return applyMiniModifiers({
|
|
712
|
+
value: base,
|
|
713
|
+
nullable: meta.nullable,
|
|
714
|
+
optional: meta.optional,
|
|
715
|
+
nullish: meta.nullish,
|
|
716
|
+
defaultValue: meta.default
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
});
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/generators/zodGenerator.tsx
|
|
723
|
+
const zodGenerator = defineGenerator({
|
|
724
|
+
name: "zod",
|
|
725
|
+
renderer: jsxRenderer,
|
|
726
|
+
schema(node, ctx) {
|
|
727
|
+
const { adapter, config, resolver, root } = ctx;
|
|
728
|
+
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options;
|
|
729
|
+
const dateType = adapter.options.dateType;
|
|
730
|
+
if (!node.name) return;
|
|
731
|
+
const mode = ctx.getMode(output);
|
|
732
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
733
|
+
const imports = adapter.getImports(node, (schemaName) => ({
|
|
734
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
735
|
+
path: resolver.resolveFile({
|
|
736
|
+
name: schemaName,
|
|
737
|
+
extname: ".ts"
|
|
738
|
+
}, {
|
|
739
|
+
root,
|
|
740
|
+
output,
|
|
741
|
+
group
|
|
742
|
+
}).path
|
|
743
|
+
}));
|
|
744
|
+
const meta = {
|
|
745
|
+
name: resolver.resolveSchemaName(node.name),
|
|
746
|
+
file: resolver.resolveFile({
|
|
747
|
+
name: node.name,
|
|
748
|
+
extname: ".ts"
|
|
749
|
+
}, {
|
|
750
|
+
root,
|
|
751
|
+
output,
|
|
752
|
+
group
|
|
753
|
+
})
|
|
754
|
+
};
|
|
755
|
+
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : void 0;
|
|
756
|
+
const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : void 0;
|
|
757
|
+
const schemaPrinter = mini ? printerZodMini({
|
|
758
|
+
guidType,
|
|
759
|
+
wrapOutput,
|
|
760
|
+
resolver,
|
|
761
|
+
cyclicSchemas,
|
|
762
|
+
nodes: printer?.nodes
|
|
763
|
+
}) : printerZod({
|
|
764
|
+
coercion,
|
|
765
|
+
guidType,
|
|
766
|
+
dateType,
|
|
767
|
+
wrapOutput,
|
|
768
|
+
resolver,
|
|
769
|
+
cyclicSchemas,
|
|
770
|
+
nodes: printer?.nodes
|
|
771
|
+
});
|
|
772
|
+
return /* @__PURE__ */ jsxs(File, {
|
|
773
|
+
baseName: meta.file.baseName,
|
|
774
|
+
path: meta.file.path,
|
|
775
|
+
meta: meta.file.meta,
|
|
776
|
+
banner: resolver.resolveBanner(adapter.inputNode, {
|
|
777
|
+
output,
|
|
778
|
+
config
|
|
779
|
+
}),
|
|
780
|
+
footer: resolver.resolveFooter(adapter.inputNode, {
|
|
781
|
+
output,
|
|
782
|
+
config
|
|
783
|
+
}),
|
|
784
|
+
children: [
|
|
785
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
786
|
+
name: isZodImport ? "z" : ["z"],
|
|
787
|
+
path: importPath,
|
|
788
|
+
isNameSpace: isZodImport
|
|
789
|
+
}),
|
|
790
|
+
mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
791
|
+
root: meta.file.path,
|
|
792
|
+
path: imp.path,
|
|
793
|
+
name: imp.name
|
|
794
|
+
}, [node.name, imp.path].join("-"))),
|
|
795
|
+
/* @__PURE__ */ jsx(Zod, {
|
|
796
|
+
name: meta.name,
|
|
797
|
+
node,
|
|
798
|
+
printer: schemaPrinter,
|
|
799
|
+
inferTypeName
|
|
800
|
+
})
|
|
801
|
+
]
|
|
802
|
+
});
|
|
803
|
+
},
|
|
804
|
+
operation(node, ctx) {
|
|
805
|
+
const { adapter, config, resolver, root } = ctx;
|
|
806
|
+
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
|
|
807
|
+
const dateType = adapter.options.dateType;
|
|
808
|
+
const mode = ctx.getMode(output);
|
|
809
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
810
|
+
const params = ast.caseParams(node.parameters, paramsCasing);
|
|
811
|
+
const meta = { file: resolver.resolveFile({
|
|
812
|
+
name: node.operationId,
|
|
813
|
+
extname: ".ts",
|
|
814
|
+
tag: node.tags[0] ?? "default",
|
|
815
|
+
path: node.path
|
|
816
|
+
}, {
|
|
817
|
+
root,
|
|
818
|
+
output,
|
|
819
|
+
group
|
|
820
|
+
}) };
|
|
821
|
+
const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : void 0;
|
|
822
|
+
function renderSchemaEntry({ schema, name, keysToOmit }) {
|
|
823
|
+
if (!schema) return null;
|
|
824
|
+
const inferTypeName = inferred ? resolver.resolveTypeName(name) : void 0;
|
|
825
|
+
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
826
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
827
|
+
path: resolver.resolveFile({
|
|
828
|
+
name: schemaName,
|
|
829
|
+
extname: ".ts"
|
|
830
|
+
}, {
|
|
831
|
+
root,
|
|
832
|
+
output,
|
|
833
|
+
group
|
|
834
|
+
}).path
|
|
835
|
+
}));
|
|
836
|
+
const schemaPrinter = mini ? printerZodMini({
|
|
837
|
+
guidType,
|
|
838
|
+
wrapOutput,
|
|
839
|
+
resolver,
|
|
840
|
+
keysToOmit,
|
|
841
|
+
cyclicSchemas,
|
|
842
|
+
nodes: printer?.nodes
|
|
843
|
+
}) : printerZod({
|
|
844
|
+
coercion,
|
|
845
|
+
guidType,
|
|
846
|
+
dateType,
|
|
847
|
+
wrapOutput,
|
|
848
|
+
resolver,
|
|
849
|
+
keysToOmit,
|
|
850
|
+
cyclicSchemas,
|
|
851
|
+
nodes: printer?.nodes
|
|
852
|
+
});
|
|
853
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
854
|
+
root: meta.file.path,
|
|
855
|
+
path: imp.path,
|
|
856
|
+
name: imp.name
|
|
857
|
+
}, [
|
|
858
|
+
name,
|
|
859
|
+
imp.path,
|
|
860
|
+
imp.name
|
|
861
|
+
].join("-"))), /* @__PURE__ */ jsx(Zod, {
|
|
862
|
+
name,
|
|
863
|
+
node: schema,
|
|
864
|
+
printer: schemaPrinter,
|
|
865
|
+
inferTypeName
|
|
866
|
+
})] });
|
|
867
|
+
}
|
|
868
|
+
const paramSchemas = params.map((param) => renderSchemaEntry({
|
|
869
|
+
schema: param.schema,
|
|
870
|
+
name: resolver.resolveParamName(node, param)
|
|
871
|
+
}));
|
|
872
|
+
const responseSchemas = node.responses.map((res) => renderSchemaEntry({
|
|
873
|
+
schema: res.schema,
|
|
874
|
+
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
875
|
+
keysToOmit: res.keysToOmit
|
|
876
|
+
}));
|
|
877
|
+
const responsesWithSchema = node.responses.filter((res) => res.schema);
|
|
878
|
+
const responseUnionSchema = responsesWithSchema.length > 0 ? (() => {
|
|
879
|
+
const responseUnionName = resolver.resolveResponseName(node);
|
|
880
|
+
if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(res.schema, (schemaName) => ({
|
|
881
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
882
|
+
path: ""
|
|
883
|
+
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseUnionName)) return null;
|
|
884
|
+
const members = responsesWithSchema.map((res) => ast.createSchema({
|
|
885
|
+
type: "ref",
|
|
886
|
+
name: resolver.resolveResponseStatusName(node, res.statusCode)
|
|
887
|
+
}));
|
|
888
|
+
return renderSchemaEntry({
|
|
889
|
+
schema: members.length === 1 ? members[0] : ast.createSchema({
|
|
890
|
+
type: "union",
|
|
891
|
+
members
|
|
892
|
+
}),
|
|
893
|
+
name: responseUnionName
|
|
894
|
+
});
|
|
895
|
+
})() : null;
|
|
896
|
+
const requestSchema = node.requestBody?.content?.[0]?.schema ? renderSchemaEntry({
|
|
897
|
+
schema: {
|
|
898
|
+
...node.requestBody.content[0].schema,
|
|
899
|
+
description: node.requestBody.description ?? node.requestBody.content[0].schema.description
|
|
900
|
+
},
|
|
901
|
+
name: resolver.resolveDataName(node),
|
|
902
|
+
keysToOmit: node.requestBody.content[0].keysToOmit
|
|
903
|
+
}) : null;
|
|
904
|
+
return /* @__PURE__ */ jsxs(File, {
|
|
905
|
+
baseName: meta.file.baseName,
|
|
906
|
+
path: meta.file.path,
|
|
907
|
+
meta: meta.file.meta,
|
|
908
|
+
banner: resolver.resolveBanner(adapter.inputNode, {
|
|
909
|
+
output,
|
|
910
|
+
config
|
|
911
|
+
}),
|
|
912
|
+
footer: resolver.resolveFooter(adapter.inputNode, {
|
|
913
|
+
output,
|
|
914
|
+
config
|
|
915
|
+
}),
|
|
916
|
+
children: [
|
|
917
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
918
|
+
name: isZodImport ? "z" : ["z"],
|
|
919
|
+
path: importPath,
|
|
920
|
+
isNameSpace: isZodImport
|
|
921
|
+
}),
|
|
922
|
+
paramSchemas,
|
|
923
|
+
responseSchemas,
|
|
924
|
+
responseUnionSchema,
|
|
925
|
+
requestSchema
|
|
926
|
+
]
|
|
927
|
+
});
|
|
928
|
+
},
|
|
929
|
+
operations(nodes, ctx) {
|
|
930
|
+
const { adapter, config, resolver, root } = ctx;
|
|
931
|
+
const { output, importPath, group, operations, paramsCasing } = ctx.options;
|
|
932
|
+
if (!operations) return;
|
|
933
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
934
|
+
const meta = { file: resolver.resolveFile({
|
|
935
|
+
name: "operations",
|
|
936
|
+
extname: ".ts"
|
|
937
|
+
}, {
|
|
938
|
+
root,
|
|
939
|
+
output,
|
|
940
|
+
group
|
|
941
|
+
}) };
|
|
942
|
+
const transformedOperations = nodes.map((node) => {
|
|
943
|
+
return {
|
|
944
|
+
node,
|
|
945
|
+
data: buildSchemaNames(node, {
|
|
946
|
+
params: ast.caseParams(node.parameters, paramsCasing),
|
|
947
|
+
resolver
|
|
948
|
+
})
|
|
949
|
+
};
|
|
950
|
+
});
|
|
951
|
+
const imports = transformedOperations.flatMap(({ node, data }) => {
|
|
952
|
+
const names = [
|
|
953
|
+
data.request,
|
|
954
|
+
...Object.values(data.responses),
|
|
955
|
+
...Object.values(data.parameters)
|
|
956
|
+
].filter(Boolean);
|
|
957
|
+
const opFile = resolver.resolveFile({
|
|
958
|
+
name: node.operationId,
|
|
959
|
+
extname: ".ts",
|
|
960
|
+
tag: node.tags[0] ?? "default",
|
|
961
|
+
path: node.path
|
|
962
|
+
}, {
|
|
963
|
+
root,
|
|
964
|
+
output,
|
|
965
|
+
group
|
|
966
|
+
});
|
|
967
|
+
return names.map((name) => /* @__PURE__ */ jsx(File.Import, {
|
|
968
|
+
name: [name],
|
|
969
|
+
root: meta.file.path,
|
|
970
|
+
path: opFile.path
|
|
971
|
+
}, [name, opFile.path].join("-")));
|
|
972
|
+
});
|
|
973
|
+
return /* @__PURE__ */ jsxs(File, {
|
|
974
|
+
baseName: meta.file.baseName,
|
|
975
|
+
path: meta.file.path,
|
|
976
|
+
meta: meta.file.meta,
|
|
977
|
+
banner: resolver.resolveBanner(adapter.inputNode, {
|
|
978
|
+
output,
|
|
979
|
+
config
|
|
980
|
+
}),
|
|
981
|
+
footer: resolver.resolveFooter(adapter.inputNode, {
|
|
982
|
+
output,
|
|
983
|
+
config
|
|
984
|
+
}),
|
|
985
|
+
children: [
|
|
986
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
987
|
+
isTypeOnly: true,
|
|
988
|
+
name: isZodImport ? "z" : ["z"],
|
|
989
|
+
path: importPath,
|
|
990
|
+
isNameSpace: isZodImport
|
|
991
|
+
}),
|
|
992
|
+
imports,
|
|
993
|
+
/* @__PURE__ */ jsx(Operations, {
|
|
994
|
+
name: "operations",
|
|
995
|
+
operations: transformedOperations
|
|
996
|
+
})
|
|
997
|
+
]
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region src/resolvers/resolverZod.ts
|
|
1003
|
+
/**
|
|
1004
|
+
* Naming convention resolver for Zod plugin.
|
|
1005
|
+
*
|
|
1006
|
+
* Provides default naming helpers using camelCase with a `Schema` suffix for schemas.
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* `resolverZod.default('list pets', 'function') // → 'listPetsSchema'`
|
|
1010
|
+
*/
|
|
1011
|
+
const resolverZod = defineResolver((ctx) => {
|
|
1012
|
+
return {
|
|
1013
|
+
name: "default",
|
|
1014
|
+
pluginName: "plugin-zod",
|
|
1015
|
+
default(name, type) {
|
|
1016
|
+
return camelCase(name, {
|
|
1017
|
+
isFile: type === "file",
|
|
1018
|
+
suffix: type ? "schema" : void 0
|
|
1019
|
+
});
|
|
1020
|
+
},
|
|
1021
|
+
resolveSchemaName(name) {
|
|
1022
|
+
return camelCase(name, { suffix: "schema" });
|
|
1023
|
+
},
|
|
1024
|
+
resolveSchemaTypeName(name) {
|
|
1025
|
+
return pascalCase(name, { suffix: "schema" });
|
|
1026
|
+
},
|
|
1027
|
+
resolveTypeName(name) {
|
|
1028
|
+
return pascalCase(name);
|
|
1029
|
+
},
|
|
1030
|
+
resolvePathName(name, type) {
|
|
1031
|
+
return ctx.default(name, type);
|
|
1032
|
+
},
|
|
1033
|
+
resolveParamName(node, param) {
|
|
1034
|
+
return ctx.resolveSchemaName(`${node.operationId} ${param.in} ${param.name}`);
|
|
1035
|
+
},
|
|
1036
|
+
resolveResponseStatusName(node, statusCode) {
|
|
1037
|
+
return ctx.resolveSchemaName(`${node.operationId} Status ${statusCode}`);
|
|
1038
|
+
},
|
|
1039
|
+
resolveDataName(node) {
|
|
1040
|
+
return ctx.resolveSchemaName(`${node.operationId} Data`);
|
|
1041
|
+
},
|
|
1042
|
+
resolveResponsesName(node) {
|
|
1043
|
+
return ctx.resolveSchemaName(`${node.operationId} Responses`);
|
|
1044
|
+
},
|
|
1045
|
+
resolveResponseName(node) {
|
|
1046
|
+
return ctx.resolveSchemaName(`${node.operationId} Response`);
|
|
1047
|
+
},
|
|
1048
|
+
resolvePathParamsName(node, param) {
|
|
1049
|
+
return ctx.resolveParamName(node, param);
|
|
1050
|
+
},
|
|
1051
|
+
resolveQueryParamsName(node, param) {
|
|
1052
|
+
return ctx.resolveParamName(node, param);
|
|
1053
|
+
},
|
|
1054
|
+
resolveHeaderParamsName(node, param) {
|
|
1055
|
+
return ctx.resolveParamName(node, param);
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
});
|
|
1059
|
+
//#endregion
|
|
63
1060
|
//#region src/plugin.ts
|
|
1061
|
+
/**
|
|
1062
|
+
* Canonical plugin name for `@kubb/plugin-zod`, used in driver lookups and warnings.
|
|
1063
|
+
*/
|
|
64
1064
|
const pluginZodName = "plugin-zod";
|
|
65
|
-
|
|
1065
|
+
/**
|
|
1066
|
+
* Generates Zod validation schemas from an OpenAPI specification.
|
|
1067
|
+
* Walks schemas and operations, delegates to generators, and writes barrel files
|
|
1068
|
+
* based on the configured `barrelType`.
|
|
1069
|
+
*
|
|
1070
|
+
* @example Zod schema generator
|
|
1071
|
+
* ```ts
|
|
1072
|
+
* import pluginZod from '@kubb/plugin-zod'
|
|
1073
|
+
* export default defineConfig({
|
|
1074
|
+
* plugins: [pluginZod({ output: { path: 'zod' } })]
|
|
1075
|
+
* })
|
|
1076
|
+
* ```
|
|
1077
|
+
*/
|
|
1078
|
+
const pluginZod = definePlugin((options) => {
|
|
66
1079
|
const { output = {
|
|
67
1080
|
path: "zod",
|
|
68
1081
|
barrelType: "named"
|
|
69
|
-
}, group, exclude = [], include, override = [],
|
|
1082
|
+
}, 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;
|
|
1083
|
+
const groupConfig = group ? {
|
|
1084
|
+
...group,
|
|
1085
|
+
name: (ctx) => {
|
|
1086
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1087
|
+
return `${camelCase(ctx.group)}Controller`;
|
|
1088
|
+
}
|
|
1089
|
+
} : void 0;
|
|
70
1090
|
return {
|
|
71
1091
|
name: pluginZodName,
|
|
72
|
-
options
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
exclude,
|
|
77
|
-
override,
|
|
78
|
-
typed,
|
|
79
|
-
dateType,
|
|
80
|
-
unknownType,
|
|
81
|
-
emptySchemaType,
|
|
82
|
-
integerType,
|
|
83
|
-
mapper,
|
|
84
|
-
importPath,
|
|
85
|
-
coercion,
|
|
86
|
-
operations,
|
|
87
|
-
inferred,
|
|
88
|
-
group,
|
|
89
|
-
wrapOutput,
|
|
90
|
-
version,
|
|
91
|
-
guidType,
|
|
92
|
-
mini,
|
|
93
|
-
usedEnumNames: {}
|
|
94
|
-
},
|
|
95
|
-
pre: [pluginOasName, typed ? pluginTsName : void 0].filter(Boolean),
|
|
96
|
-
resolvePath(baseName, pathMode, options) {
|
|
97
|
-
const root = path.resolve(this.config.root, this.config.output.path);
|
|
98
|
-
if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single")
|
|
99
|
-
/**
|
|
100
|
-
* when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
|
|
101
|
-
* Other plugins then need to call addOrAppend instead of just add from the fileManager class
|
|
102
|
-
*/
|
|
103
|
-
return path.resolve(root, output.path);
|
|
104
|
-
if (group && (options?.group?.path || options?.group?.tag)) {
|
|
105
|
-
const groupName = group?.name ? group.name : (ctx) => {
|
|
106
|
-
if (group?.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
107
|
-
return `${camelCase(ctx.group)}Controller`;
|
|
108
|
-
};
|
|
109
|
-
return path.resolve(root, output.path, groupName({ group: group.type === "path" ? options.group.path : options.group.tag }), baseName);
|
|
110
|
-
}
|
|
111
|
-
return path.resolve(root, output.path, baseName);
|
|
112
|
-
},
|
|
113
|
-
resolveName(name, type) {
|
|
114
|
-
let resolvedName = camelCase(name, {
|
|
115
|
-
suffix: type ? "schema" : void 0,
|
|
116
|
-
isFile: type === "file"
|
|
117
|
-
});
|
|
118
|
-
if (type === "type") resolvedName = pascalCase(resolvedName);
|
|
119
|
-
if (type) return transformers?.name?.(resolvedName, type) || resolvedName;
|
|
120
|
-
return resolvedName;
|
|
121
|
-
},
|
|
122
|
-
async install() {
|
|
123
|
-
const root = path.resolve(this.config.root, this.config.output.path);
|
|
124
|
-
const mode = getMode(path.resolve(root, output.path));
|
|
125
|
-
const oas = await this.getOas();
|
|
126
|
-
if (this.plugin.options.typed && this.plugin.options.version === "3") await this.addFile({
|
|
127
|
-
baseName: "ToZod.ts",
|
|
128
|
-
path: path.resolve(root, ".kubb/ToZod.ts"),
|
|
129
|
-
sources: [{
|
|
130
|
-
name: "ToZod",
|
|
131
|
-
value: source
|
|
132
|
-
}],
|
|
133
|
-
imports: [],
|
|
134
|
-
exports: []
|
|
135
|
-
});
|
|
136
|
-
const schemaFiles = await new SchemaGenerator(this.plugin.options, {
|
|
137
|
-
fabric: this.fabric,
|
|
138
|
-
oas,
|
|
139
|
-
driver: this.driver,
|
|
140
|
-
events: this.events,
|
|
141
|
-
plugin: this.plugin,
|
|
142
|
-
contentType,
|
|
143
|
-
include: void 0,
|
|
144
|
-
override,
|
|
145
|
-
mode,
|
|
146
|
-
output: output.path
|
|
147
|
-
}).build(...generators);
|
|
148
|
-
await this.upsertFile(...schemaFiles);
|
|
149
|
-
const operationFiles = await new OperationGenerator(this.plugin.options, {
|
|
150
|
-
fabric: this.fabric,
|
|
151
|
-
oas,
|
|
152
|
-
driver: this.driver,
|
|
153
|
-
events: this.events,
|
|
154
|
-
plugin: this.plugin,
|
|
155
|
-
contentType,
|
|
1092
|
+
options,
|
|
1093
|
+
hooks: { "kubb:plugin:setup"(ctx) {
|
|
1094
|
+
ctx.setOptions({
|
|
1095
|
+
output,
|
|
156
1096
|
exclude,
|
|
157
1097
|
include,
|
|
158
1098
|
override,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
1099
|
+
group: groupConfig,
|
|
1100
|
+
typed,
|
|
1101
|
+
importPath,
|
|
1102
|
+
coercion,
|
|
1103
|
+
operations,
|
|
1104
|
+
inferred,
|
|
1105
|
+
guidType,
|
|
1106
|
+
mini,
|
|
1107
|
+
wrapOutput,
|
|
1108
|
+
paramsCasing,
|
|
1109
|
+
printer
|
|
167
1110
|
});
|
|
168
|
-
|
|
169
|
-
|
|
1111
|
+
ctx.setResolver(userResolver ? {
|
|
1112
|
+
...resolverZod,
|
|
1113
|
+
...userResolver
|
|
1114
|
+
} : resolverZod);
|
|
1115
|
+
if (userTransformer) ctx.setTransformer(userTransformer);
|
|
1116
|
+
ctx.addGenerator(zodGenerator);
|
|
1117
|
+
for (const gen of userGenerators) ctx.addGenerator(gen);
|
|
1118
|
+
} }
|
|
170
1119
|
};
|
|
171
1120
|
});
|
|
172
1121
|
//#endregion
|
|
173
|
-
export { pluginZod, pluginZodName };
|
|
1122
|
+
export { pluginZod as default, pluginZod, pluginZodName, printerZod, printerZodMini, resolverZod, zodGenerator };
|
|
174
1123
|
|
|
175
1124
|
//# sourceMappingURL=index.js.map
|