@kubb/plugin-zod 5.9.0-canary.20261111T122006 → 5.9.0
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 +760 -806
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +761 -807
- package/dist/index.js.map +1 -1
- package/docs/guides/customization.md +31 -23
- package/docs/options.md +2 -1
- package/kubb-extension.json +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,143 +1,407 @@
|
|
|
1
|
-
import { Diagnostics, Resolver, addGenerator, ast,
|
|
1
|
+
import { Diagnostics, Resolver, addGenerator, ast, casing, createPrinter, createResolver, defineExtension, defineGenerator, setMacros, setResolver, text } from "@kubb/kit";
|
|
2
2
|
import { Const, File, Function, Type, jsxRenderer } from "@kubb/renderer-jsx";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
4
|
-
// ../../internals/
|
|
4
|
+
// ../../internals/utils/src/reserved.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
* JavaScript and Java reserved words.
|
|
7
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
8
|
+
*/
|
|
9
|
+
const reservedWords = /* @__PURE__ */ new Set([
|
|
10
|
+
"abstract",
|
|
11
|
+
"arguments",
|
|
12
|
+
"await",
|
|
13
|
+
"boolean",
|
|
14
|
+
"break",
|
|
15
|
+
"byte",
|
|
16
|
+
"case",
|
|
17
|
+
"catch",
|
|
18
|
+
"char",
|
|
19
|
+
"class",
|
|
20
|
+
"const",
|
|
21
|
+
"continue",
|
|
22
|
+
"debugger",
|
|
23
|
+
"default",
|
|
24
|
+
"delete",
|
|
25
|
+
"do",
|
|
26
|
+
"double",
|
|
27
|
+
"else",
|
|
28
|
+
"enum",
|
|
29
|
+
"eval",
|
|
30
|
+
"export",
|
|
31
|
+
"extends",
|
|
32
|
+
"false",
|
|
33
|
+
"final",
|
|
34
|
+
"finally",
|
|
35
|
+
"float",
|
|
36
|
+
"for",
|
|
37
|
+
"function",
|
|
38
|
+
"goto",
|
|
39
|
+
"if",
|
|
40
|
+
"implements",
|
|
41
|
+
"import",
|
|
42
|
+
"in",
|
|
43
|
+
"instanceof",
|
|
44
|
+
"int",
|
|
45
|
+
"interface",
|
|
46
|
+
"let",
|
|
47
|
+
"long",
|
|
48
|
+
"native",
|
|
49
|
+
"new",
|
|
50
|
+
"null",
|
|
51
|
+
"package",
|
|
52
|
+
"private",
|
|
53
|
+
"protected",
|
|
54
|
+
"public",
|
|
55
|
+
"return",
|
|
56
|
+
"short",
|
|
57
|
+
"static",
|
|
58
|
+
"super",
|
|
59
|
+
"switch",
|
|
60
|
+
"synchronized",
|
|
61
|
+
"this",
|
|
62
|
+
"throw",
|
|
63
|
+
"throws",
|
|
64
|
+
"transient",
|
|
65
|
+
"true",
|
|
66
|
+
"try",
|
|
67
|
+
"typeof",
|
|
68
|
+
"var",
|
|
69
|
+
"void",
|
|
70
|
+
"volatile",
|
|
71
|
+
"while",
|
|
72
|
+
"with",
|
|
73
|
+
"yield",
|
|
74
|
+
"Array",
|
|
75
|
+
"Date",
|
|
76
|
+
"hasOwnProperty",
|
|
77
|
+
"Infinity",
|
|
78
|
+
"isFinite",
|
|
79
|
+
"isNaN",
|
|
80
|
+
"isPrototypeOf",
|
|
81
|
+
"length",
|
|
82
|
+
"Math",
|
|
83
|
+
"name",
|
|
84
|
+
"NaN",
|
|
85
|
+
"Number",
|
|
86
|
+
"Object",
|
|
87
|
+
"prototype",
|
|
88
|
+
"String",
|
|
89
|
+
"toString",
|
|
90
|
+
"undefined",
|
|
91
|
+
"valueOf"
|
|
92
|
+
]);
|
|
93
|
+
/**
|
|
94
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
10
95
|
*
|
|
11
96
|
* @example
|
|
12
97
|
* ```ts
|
|
13
|
-
*
|
|
98
|
+
* isValidVarName('status') // true
|
|
99
|
+
* isValidVarName('class') // false (reserved word)
|
|
100
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
14
101
|
* ```
|
|
15
102
|
*/
|
|
16
|
-
function
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
extname,
|
|
20
|
-
tag: node.tags[0] ?? "default",
|
|
21
|
-
path: node.path
|
|
22
|
-
};
|
|
103
|
+
function isValidVarName(name) {
|
|
104
|
+
if (!name || reservedWords.has(name)) return false;
|
|
105
|
+
return isIdentifier(name);
|
|
23
106
|
}
|
|
24
107
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* operation. The plugin itself and every plugin that imports from it call this, so they all get
|
|
28
|
-
* the same path.
|
|
108
|
+
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
109
|
+
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
29
110
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* Only the file name changes. The declarations keep their names.
|
|
111
|
+
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
112
|
+
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
113
|
+
* variable, type, or function names.
|
|
34
114
|
*
|
|
35
|
-
* @example
|
|
115
|
+
* @example
|
|
36
116
|
* ```ts
|
|
37
|
-
*
|
|
117
|
+
* ensureValidVarName('409') // '_409'
|
|
118
|
+
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
119
|
+
* ensureValidVarName('Pet') // 'Pet'
|
|
120
|
+
* ensureValidVarName('class') // '_class'
|
|
38
121
|
* ```
|
|
39
122
|
*/
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const location = {
|
|
44
|
-
root,
|
|
45
|
-
output,
|
|
46
|
-
group: options.group ?? void 0
|
|
47
|
-
};
|
|
48
|
-
return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => {
|
|
49
|
-
const schemaPaths = memo(`${resolver.pluginName}:schemaFilePaths`, () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.name ? [resolver.file({
|
|
50
|
-
name: schema.name,
|
|
51
|
-
extname: ".ts",
|
|
52
|
-
...location
|
|
53
|
-
}).path.toLowerCase()] : [])));
|
|
54
|
-
let file = resolver.file({
|
|
55
|
-
...operationFileEntry(node, node.operationId),
|
|
56
|
-
...location
|
|
57
|
-
});
|
|
58
|
-
for (let name = `${node.operationId}Operation`; schemaPaths.has(file.path.toLowerCase()); name += "Operation") {
|
|
59
|
-
const next = resolver.file({
|
|
60
|
-
...operationFileEntry(node, name),
|
|
61
|
-
...location
|
|
62
|
-
});
|
|
63
|
-
if (next.path === file.path) break;
|
|
64
|
-
file = next;
|
|
65
|
-
}
|
|
66
|
-
return file;
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
function baseContentType(contentType) {
|
|
70
|
-
return contentType.split(";")[0].trim().toLowerCase();
|
|
123
|
+
function ensureValidVarName(name) {
|
|
124
|
+
if (!name || isValidVarName(name)) return name;
|
|
125
|
+
return `_${name}`;
|
|
71
126
|
}
|
|
72
127
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
128
|
+
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
129
|
+
*
|
|
130
|
+
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
131
|
+
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
132
|
+
* deciding whether an object key needs quoting.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* isIdentifier('name') // true
|
|
137
|
+
* isIdentifier('x-total')// false
|
|
138
|
+
* ```
|
|
75
139
|
*/
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
return Boolean(entry.stream) && baseType !== "text/event-stream" && !baseType.startsWith("multipart/");
|
|
79
|
-
}
|
|
80
|
-
function isJsonContentType(baseType) {
|
|
81
|
-
return baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json";
|
|
140
|
+
function isIdentifier(name) {
|
|
141
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
82
142
|
}
|
|
143
|
+
// ../../internals/utils/src/codegen.ts
|
|
83
144
|
/**
|
|
84
|
-
*
|
|
145
|
+
* Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
|
|
146
|
+
* level and closing the brace at column zero. Entries that are themselves multi-line objects indent
|
|
147
|
+
* cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
|
|
85
148
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* buildObject(['id: z.number()', 'name: z.string()'])
|
|
152
|
+
* // '{\n id: z.number(),\n name: z.string(),\n}'
|
|
153
|
+
* ```
|
|
90
154
|
*/
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (baseType === "text/event-stream" || isJsonLines(entry)) return "stream";
|
|
95
|
-
if (isJsonContentType(baseType)) return void 0;
|
|
96
|
-
if (entry.schema?.type === "blob") return "blob";
|
|
97
|
-
if (baseType.startsWith("text/")) return "text";
|
|
98
|
-
if (isBinaryContentType(baseType)) return "blob";
|
|
99
|
-
}
|
|
100
|
-
function isBinaryContentType(baseType) {
|
|
101
|
-
return baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType);
|
|
155
|
+
function buildObject(entries) {
|
|
156
|
+
if (entries.length === 0) return "{}";
|
|
157
|
+
return `{\n${entries.map((entry) => `${text.indent({ text: entry })},`).join("\n")}\n}`;
|
|
102
158
|
}
|
|
103
159
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
160
|
+
* Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on
|
|
161
|
+
* one line when no item spans multiple lines, and otherwise puts each item on its own line, indented
|
|
162
|
+
* one level with a trailing comma and the closing bracket at column zero. Used for member lists such
|
|
163
|
+
* as `z.union([…])` and `z.array([…])`.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* buildList(['z.string()', 'z.number()'])
|
|
168
|
+
* // '[z.string(), z.number()]'
|
|
169
|
+
* ```
|
|
106
170
|
*/
|
|
107
|
-
function
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
113
|
-
if (parts.length === 0) return "Unknown";
|
|
114
|
-
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
171
|
+
function buildList(items, brackets = ["[", "]"]) {
|
|
172
|
+
const [open, close] = brackets;
|
|
173
|
+
if (items.length === 0) return `${open}${close}`;
|
|
174
|
+
if (!items.some((item) => item.includes("\n"))) return `${open}${items.join(", ")}${close}`;
|
|
175
|
+
return `${open}\n${items.map((item) => `${text.indent({ text: item })},`).join("\n")}\n${close}`;
|
|
115
176
|
}
|
|
116
177
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
178
|
+
* Emits a lazy getter for a circular-ref property position, `get name() { return body }`. The key
|
|
179
|
+
* is quoted only when it is not a valid identifier. Used by the string printers to defer evaluation
|
|
180
|
+
* of a recursive schema until first access.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* lazyGetter({ name: 'parent', body: 'z.lazy(() => Pet)' })
|
|
185
|
+
* // "get parent() { return z.lazy(() => Pet) }"
|
|
186
|
+
* ```
|
|
119
187
|
*/
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
return baseName + suffix;
|
|
188
|
+
function lazyGetter({ name, body }) {
|
|
189
|
+
return `get ${objectKey(name)}() { return ${body} }`;
|
|
123
190
|
}
|
|
124
191
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
192
|
+
* An object key, quoted only when it is not identifier-shaped. Reserved words such as `class` stay bare.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* objectKey('x-total') // "'x-total'"
|
|
197
|
+
* ```
|
|
129
198
|
*/
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
199
|
+
function objectKey(name) {
|
|
200
|
+
return isIdentifier(name) ? name : text.quote(name);
|
|
201
|
+
}
|
|
202
|
+
// ../../internals/utils/src/fs.ts
|
|
203
|
+
/**
|
|
204
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
205
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
206
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
207
|
+
*
|
|
208
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
209
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
210
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
211
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
212
|
+
*
|
|
213
|
+
* @example Nested path from a dotted name
|
|
214
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
215
|
+
*
|
|
216
|
+
* @example PascalCase the final segment
|
|
217
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
218
|
+
*
|
|
219
|
+
* @example Suffix applied to the final segment only
|
|
220
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
221
|
+
*/
|
|
222
|
+
function toFilePath(name, caseLast = casing.camel) {
|
|
223
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
224
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : casing.camel(part)).filter(Boolean).join("/");
|
|
225
|
+
}
|
|
226
|
+
// ../../internals/utils/src/strings.ts
|
|
227
|
+
const NEEDS_UNICODE = /(?<!\\)(?:\\\\)*\\[pPu]\{|[\u{10000}-\u{10FFFF}]/u;
|
|
228
|
+
const KEPT_ESCAPE = /[A-Za-z0-9^$\\.*+?()[\]{}|/]/;
|
|
229
|
+
/**
|
|
230
|
+
* Drops the backslash from escapes that the `u` flag rejects: an escaped punctuation character that is
|
|
231
|
+
* not a syntax character, and `\-` outside a class. Both match the same character without the backslash.
|
|
232
|
+
*/
|
|
233
|
+
function dropIdentityEscapes(pattern) {
|
|
234
|
+
let inClass = false;
|
|
235
|
+
return pattern.replace(/\\(.)|[[\]]/gs, (match, escaped) => {
|
|
236
|
+
if (escaped === void 0) {
|
|
237
|
+
inClass = match === "[";
|
|
238
|
+
return match;
|
|
239
|
+
}
|
|
240
|
+
return KEPT_ESCAPE.test(escaped) || escaped === "-" && inClass ? match : escaped;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Builds the `RegExp` for a pattern, adding the `u` flag when it uses `\p{...}`, `\P{...}` or `\u{...}`, or
|
|
245
|
+
* holds a character outside the BMP. A pattern that is not valid under `u` keeps the flags it came with.
|
|
246
|
+
*/
|
|
247
|
+
function toRegExp(pattern, flags) {
|
|
248
|
+
if (flags?.includes("u") || !NEEDS_UNICODE.test(pattern)) return new RegExp(pattern, flags);
|
|
249
|
+
try {
|
|
250
|
+
return new RegExp(dropIdentityEscapes(pattern), `${flags ?? ""}u`);
|
|
251
|
+
} catch {
|
|
252
|
+
return new RegExp(pattern, flags);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
|
|
257
|
+
* Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
|
|
258
|
+
* Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
|
|
263
|
+
* toRegExpString('^(?im)foo', null) // '/^foo/im'
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
function toRegExpString(pattern, func = "RegExp") {
|
|
267
|
+
const raw = text.unquote(pattern);
|
|
268
|
+
const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
|
|
269
|
+
const replacementTarget = match?.[1] ?? "";
|
|
270
|
+
const matchedFlags = match?.[2];
|
|
271
|
+
const { source, flags } = toRegExp(raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, ""), matchedFlags);
|
|
272
|
+
if (func === null) return `/${source}/${flags}`;
|
|
273
|
+
return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
|
|
274
|
+
}
|
|
275
|
+
// ../../internals/shared/src/operation.ts
|
|
276
|
+
/**
|
|
277
|
+
* Builds the `ResolverFileParams` every operation generator passes to
|
|
278
|
+
* `resolver.file`: a file named `name`, tagged by the operation's first
|
|
279
|
+
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
280
|
+
* that was repeated at dozens of call sites across the client and query plugins.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
function operationFileEntry(node, name, extname = ".ts") {
|
|
288
|
+
return {
|
|
289
|
+
name,
|
|
290
|
+
extname,
|
|
291
|
+
tag: node.tags[0] ?? "default",
|
|
292
|
+
path: node.path
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Resolves the file a schema-emitting plugin (`plugin-ts`, `plugin-zod`, `plugin-faker`) writes
|
|
297
|
+
* `node` to, cached in `ctx.memo` under the resolver's plugin name and the
|
|
298
|
+
* operation. The plugin itself and every plugin that imports from it call this, so they all get
|
|
299
|
+
* the same path.
|
|
300
|
+
*
|
|
301
|
+
* When that path matches the file of a component schema in `ctx.driver.inputNode`, ignoring case, the file name
|
|
302
|
+
* gets an `Operation` suffix: operation `point` goes to `PointOperation.ts` next to schema `Point.ts`.
|
|
303
|
+
* Otherwise the two merge into one file, or overwrite each other on a case-insensitive file system.
|
|
304
|
+
* Only the file name changes. The declarations keep their names.
|
|
305
|
+
*
|
|
306
|
+
* @example Resolve `plugin-ts`'s file for the current operation
|
|
307
|
+
* ```ts
|
|
308
|
+
* const fileTs = resolveOperationFile({ ctx, node, resolver: tsResolver, root, output })
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
function resolveOperationFile(options) {
|
|
312
|
+
const { ctx, node, resolver, root, output } = options;
|
|
313
|
+
const { memo } = ctx;
|
|
314
|
+
const location = {
|
|
315
|
+
root,
|
|
316
|
+
output,
|
|
317
|
+
group: options.group ?? void 0
|
|
318
|
+
};
|
|
319
|
+
return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => {
|
|
320
|
+
const schemaPaths = memo(`${resolver.pluginName}:schemaFilePaths`, () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.name ? [resolver.file({
|
|
321
|
+
name: schema.name,
|
|
322
|
+
extname: ".ts",
|
|
323
|
+
...location
|
|
324
|
+
}).path.toLowerCase()] : [])));
|
|
325
|
+
let file = resolver.file({
|
|
326
|
+
...operationFileEntry(node, node.operationId),
|
|
327
|
+
...location
|
|
328
|
+
});
|
|
329
|
+
for (let name = `${node.operationId}Operation`; schemaPaths.has(file.path.toLowerCase()); name += "Operation") {
|
|
330
|
+
const next = resolver.file({
|
|
331
|
+
...operationFileEntry(node, name),
|
|
332
|
+
...location
|
|
333
|
+
});
|
|
334
|
+
if (next.path === file.path) break;
|
|
335
|
+
file = next;
|
|
336
|
+
}
|
|
337
|
+
return file;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
|
|
342
|
+
*/
|
|
343
|
+
function getStreamFraming(entry) {
|
|
344
|
+
if (ast.mediaType.getBase(entry) === "text/event-stream") return "events";
|
|
345
|
+
return entry.stream && ast.mediaType.isStream(entry) ? "lines" : void 0;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Parser for one response content entry.
|
|
349
|
+
*
|
|
350
|
+
* JSON stays unset so the runtime detects it. `text/event-stream` and JSON Lines are streams. A binary schema
|
|
351
|
+
* (`format: binary` / `contentMediaType`) is a blob even when the media type is not a well-known
|
|
352
|
+
* binary type. Other `text/*` is text, and `application/octet-stream`, `application/pdf`,
|
|
353
|
+
* `image/*`, `audio/*`, and `video/*` are blobs.
|
|
354
|
+
*/
|
|
355
|
+
function responseTypeForContent(entry) {
|
|
356
|
+
const baseType = ast.mediaType.getBase(entry);
|
|
357
|
+
if (!baseType) return void 0;
|
|
358
|
+
if (getStreamFraming(entry)) return "stream";
|
|
359
|
+
if (ast.mediaType.isJson(baseType)) return void 0;
|
|
360
|
+
if (entry.schema?.type === "blob") return "blob";
|
|
361
|
+
if (baseType.startsWith("text/")) return "text";
|
|
362
|
+
if (isBinaryContentType(baseType)) return "blob";
|
|
363
|
+
}
|
|
364
|
+
function isBinaryContentType(baseType) {
|
|
365
|
+
return baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
369
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
370
|
+
*/
|
|
371
|
+
function getContentTypeSuffix(contentType) {
|
|
372
|
+
const baseType = contentType.split(";")[0].trim();
|
|
373
|
+
if (baseType === "application/json") return "Json";
|
|
374
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
375
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
376
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
377
|
+
if (parts.length === 0) return "Unknown";
|
|
378
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
382
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
383
|
+
*/
|
|
384
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
385
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
386
|
+
return baseName + suffix;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Returns the entry with the schema of the value the client returns: `string` for a body read as text, such as `Pet` on
|
|
390
|
+
* `application/xml`, and `blob` for `image/*`, `audio/*`, `video/*`, PDF, and octet-stream. A binary schema elsewhere is a `Blob`
|
|
391
|
+
* only through `parseAs: 'blob'`, which needs every entry in `entries` binary. JSON, vendor (`application/vnd.*`), stream,
|
|
392
|
+
* schemaless, and wildcard entries keep their schema. `entries` is the response's full list of content types.
|
|
393
|
+
*/
|
|
394
|
+
function toResponseContent(entry, entries = [entry]) {
|
|
395
|
+
const { contentType, schema } = entry;
|
|
396
|
+
if (!schema || entry.stream) return entry;
|
|
397
|
+
const baseType = ast.mediaType.getBase(contentType);
|
|
398
|
+
const resolved = ast.ref.getSchema(schema);
|
|
399
|
+
if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
|
|
400
|
+
...entry,
|
|
401
|
+
schema: ast.factory.createSchema({ type: "blob" }),
|
|
402
|
+
keysToOmit: null
|
|
403
|
+
};
|
|
404
|
+
const isPlainMediaType = Boolean(baseType) && !ast.mediaType.isJson(baseType) && !baseType.includes("*");
|
|
141
405
|
if (resolved.type === "blob") return isPlainMediaType && !entries.every((item) => item.schema?.type === "blob") ? {
|
|
142
406
|
...entry,
|
|
143
407
|
schema: ast.factory.createSchema({ type: "string" }),
|
|
@@ -180,50 +444,6 @@ function resolveContentTypeVariants(entries, baseName) {
|
|
|
180
444
|
};
|
|
181
445
|
});
|
|
182
446
|
}
|
|
183
|
-
const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
|
|
184
|
-
/**
|
|
185
|
-
* An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
|
|
186
|
-
* per property, typed and serialized like any other.
|
|
187
|
-
* Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
|
|
188
|
-
*/
|
|
189
|
-
function spreadQuerystring(param) {
|
|
190
|
-
const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
|
|
191
|
-
const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
|
|
192
|
-
if (schema?.type !== "object" || !formEncoded) return [param];
|
|
193
|
-
return schema.properties.map((property) => ast.factory.createParameter({
|
|
194
|
-
name: property.name,
|
|
195
|
-
in: "query",
|
|
196
|
-
schema: property.schema,
|
|
197
|
-
required: param.required && property.required
|
|
198
|
-
}));
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
|
|
202
|
-
* group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
|
|
203
|
-
* (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
|
|
204
|
-
* same parameters once per plugin.
|
|
205
|
-
*/
|
|
206
|
-
function getOperationParameters(node) {
|
|
207
|
-
const cached = operationParameterGroupsByNode.get(node);
|
|
208
|
-
if (cached) return cached;
|
|
209
|
-
const dedupe = (params) => {
|
|
210
|
-
const seen = /* @__PURE__ */ new Set();
|
|
211
|
-
return params.filter((param) => {
|
|
212
|
-
if (seen.has(param.name)) return false;
|
|
213
|
-
seen.add(param.name);
|
|
214
|
-
return true;
|
|
215
|
-
});
|
|
216
|
-
};
|
|
217
|
-
const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
|
|
218
|
-
const groups = {
|
|
219
|
-
path: group("path"),
|
|
220
|
-
query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
|
|
221
|
-
header: group("header"),
|
|
222
|
-
cookie: group("cookie")
|
|
223
|
-
};
|
|
224
|
-
operationParameterGroupsByNode.set(node, groups);
|
|
225
|
-
return groups;
|
|
226
|
-
}
|
|
227
447
|
/**
|
|
228
448
|
* Whether the operation declares more than one request media type with a schema, so a caller must pick one on `contentType`.
|
|
229
449
|
*/
|
|
@@ -239,8 +459,8 @@ function hasRequestContentTypeChoice(node) {
|
|
|
239
459
|
* than a record.
|
|
240
460
|
*/
|
|
241
461
|
function buildOptionsSchema(node, resolver, { pairContentTypes = false } = {}) {
|
|
242
|
-
const { path, query, header, cookie } =
|
|
243
|
-
const hasBody = Boolean(
|
|
462
|
+
const { path, query, header, cookie } = ast.http.groupParameters(node);
|
|
463
|
+
const hasBody = Boolean(ast.http.requestBody.getSchema(node));
|
|
244
464
|
const createNever = () => ast.factory.createSchema({
|
|
245
465
|
type: "never",
|
|
246
466
|
primitive: void 0,
|
|
@@ -313,438 +533,101 @@ function buildOptionsSchema(node, resolver, { pairContentTypes = false } = {}) {
|
|
|
313
533
|
required: index > 0,
|
|
314
534
|
schema: ast.factory.createSchema({
|
|
315
535
|
type: "union",
|
|
316
|
-
members: [contentType, ast.factory.createSchema({
|
|
317
|
-
type: "object",
|
|
318
|
-
primitive: "object",
|
|
319
|
-
properties: [ast.factory.createProperty({
|
|
320
|
-
name: "request",
|
|
321
|
-
required: true,
|
|
322
|
-
schema: contentType
|
|
323
|
-
})]
|
|
324
|
-
})]
|
|
325
|
-
})
|
|
326
|
-
})]);
|
|
327
|
-
})
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* The schema a status occupies in the `<Name>Responses` record. A status that documents several
|
|
332
|
-
* content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
|
|
333
|
-
* on `result.parsed`, while the standalone `<Name>StatusNNN` alias stays the plain body union that the
|
|
334
|
-
* query hooks and `result.data` use.
|
|
335
|
-
*/
|
|
336
|
-
function buildResponseRecordEntry(node, res, resolver) {
|
|
337
|
-
const statusName = resolver.response.status(node, res.statusCode);
|
|
338
|
-
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
339
|
-
if (variants.length <= 1) return ast.factory.createSchema({
|
|
340
|
-
type: "ref",
|
|
341
|
-
name: statusName
|
|
342
|
-
});
|
|
343
|
-
return ast.factory.createSchema({
|
|
344
|
-
type: "union",
|
|
345
|
-
members: resolveContentTypeVariants(variants, statusName).map((variant) => ast.factory.createSchema({
|
|
346
|
-
type: "object",
|
|
347
|
-
primitive: "object",
|
|
348
|
-
properties: [ast.factory.createProperty({
|
|
349
|
-
name: "contentType",
|
|
350
|
-
required: true,
|
|
351
|
-
schema: ast.factory.createSchema({
|
|
352
|
-
type: "enum",
|
|
353
|
-
enumValues: [baseContentType(variant.contentType)]
|
|
354
|
-
})
|
|
355
|
-
}), ast.factory.createProperty({
|
|
356
|
-
name: "data",
|
|
357
|
-
required: true,
|
|
358
|
-
schema: ast.factory.createSchema({
|
|
359
|
-
type: "ref",
|
|
360
|
-
name: variant.name
|
|
361
|
-
})
|
|
362
|
-
})]
|
|
363
|
-
}))
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Builds the per-status `<Name>Responses` record for an operation, referencing the already-resolved
|
|
368
|
-
* `<Name>StatusNNN` names. Shared by `@kubb/plugin-ts`'s `Responses` type and `@kubb/plugin-zod`'s
|
|
369
|
-
* inferred responses schema, so both emit the same shape from the same inputs.
|
|
370
|
-
*
|
|
371
|
-
* Always emits the keyed record, even when an operation declares no responses. An operation with no
|
|
372
|
-
* responses renders as an empty object, which keeps every consumer's import (for example the axios
|
|
373
|
-
* SDK's `RequestResult<XResponses>`) resolvable instead of pointing at a missing export.
|
|
374
|
-
*/
|
|
375
|
-
function buildResponses(node, resolver) {
|
|
376
|
-
return ast.factory.createSchema({
|
|
377
|
-
type: "object",
|
|
378
|
-
primitive: "object",
|
|
379
|
-
properties: node.responses.map((res) => ast.factory.createProperty({
|
|
380
|
-
name: String(res.statusCode),
|
|
381
|
-
required: true,
|
|
382
|
-
schema: buildResponseRecordEntry(node, res, resolver)
|
|
383
|
-
}))
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
function getStatusCodeNumber(statusCode) {
|
|
387
|
-
const code = Number(statusCode);
|
|
388
|
-
return Number.isNaN(code) ? null : code;
|
|
389
|
-
}
|
|
390
|
-
function isSuccessStatusCode(statusCode) {
|
|
391
|
-
const code = getStatusCodeNumber(statusCode);
|
|
392
|
-
return code !== null && code >= 200 && code < 300;
|
|
393
|
-
}
|
|
394
|
-
function getSuccessResponses(responses) {
|
|
395
|
-
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
396
|
-
}
|
|
397
|
-
// ../../internals/shared/src/adapter.ts
|
|
398
|
-
/**
|
|
399
|
-
* Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
|
|
400
|
-
* so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
|
|
401
|
-
*
|
|
402
|
-
* Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
|
|
403
|
-
* clear, actionable error at the point of use.
|
|
404
|
-
*
|
|
405
|
-
* @example
|
|
406
|
-
* ```ts
|
|
407
|
-
* const { dateType } = getOasAdapter(ctx.adapter).options
|
|
408
|
-
* ```
|
|
409
|
-
*/
|
|
410
|
-
function getOasAdapter(adapter) {
|
|
411
|
-
if (adapter.name !== "oas") throw new Diagnostics.Error({
|
|
412
|
-
code: Diagnostics.code.adapterRequired,
|
|
413
|
-
severity: "error",
|
|
414
|
-
message: `Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}".`,
|
|
415
|
-
help: "Set `adapter: adapterOas()` in kubb.config.ts.",
|
|
416
|
-
location: { kind: "config" }
|
|
417
|
-
});
|
|
418
|
-
return adapter;
|
|
419
|
-
}
|
|
420
|
-
// ../../internals/utils/src/reserved.ts
|
|
421
|
-
/**
|
|
422
|
-
* JavaScript and Java reserved words.
|
|
423
|
-
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
424
|
-
*/
|
|
425
|
-
const reservedWords = /* @__PURE__ */ new Set([
|
|
426
|
-
"abstract",
|
|
427
|
-
"arguments",
|
|
428
|
-
"await",
|
|
429
|
-
"boolean",
|
|
430
|
-
"break",
|
|
431
|
-
"byte",
|
|
432
|
-
"case",
|
|
433
|
-
"catch",
|
|
434
|
-
"char",
|
|
435
|
-
"class",
|
|
436
|
-
"const",
|
|
437
|
-
"continue",
|
|
438
|
-
"debugger",
|
|
439
|
-
"default",
|
|
440
|
-
"delete",
|
|
441
|
-
"do",
|
|
442
|
-
"double",
|
|
443
|
-
"else",
|
|
444
|
-
"enum",
|
|
445
|
-
"eval",
|
|
446
|
-
"export",
|
|
447
|
-
"extends",
|
|
448
|
-
"false",
|
|
449
|
-
"final",
|
|
450
|
-
"finally",
|
|
451
|
-
"float",
|
|
452
|
-
"for",
|
|
453
|
-
"function",
|
|
454
|
-
"goto",
|
|
455
|
-
"if",
|
|
456
|
-
"implements",
|
|
457
|
-
"import",
|
|
458
|
-
"in",
|
|
459
|
-
"instanceof",
|
|
460
|
-
"int",
|
|
461
|
-
"interface",
|
|
462
|
-
"let",
|
|
463
|
-
"long",
|
|
464
|
-
"native",
|
|
465
|
-
"new",
|
|
466
|
-
"null",
|
|
467
|
-
"package",
|
|
468
|
-
"private",
|
|
469
|
-
"protected",
|
|
470
|
-
"public",
|
|
471
|
-
"return",
|
|
472
|
-
"short",
|
|
473
|
-
"static",
|
|
474
|
-
"super",
|
|
475
|
-
"switch",
|
|
476
|
-
"synchronized",
|
|
477
|
-
"this",
|
|
478
|
-
"throw",
|
|
479
|
-
"throws",
|
|
480
|
-
"transient",
|
|
481
|
-
"true",
|
|
482
|
-
"try",
|
|
483
|
-
"typeof",
|
|
484
|
-
"var",
|
|
485
|
-
"void",
|
|
486
|
-
"volatile",
|
|
487
|
-
"while",
|
|
488
|
-
"with",
|
|
489
|
-
"yield",
|
|
490
|
-
"Array",
|
|
491
|
-
"Date",
|
|
492
|
-
"hasOwnProperty",
|
|
493
|
-
"Infinity",
|
|
494
|
-
"isFinite",
|
|
495
|
-
"isNaN",
|
|
496
|
-
"isPrototypeOf",
|
|
497
|
-
"length",
|
|
498
|
-
"Math",
|
|
499
|
-
"name",
|
|
500
|
-
"NaN",
|
|
501
|
-
"Number",
|
|
502
|
-
"Object",
|
|
503
|
-
"prototype",
|
|
504
|
-
"String",
|
|
505
|
-
"toString",
|
|
506
|
-
"undefined",
|
|
507
|
-
"valueOf"
|
|
508
|
-
]);
|
|
509
|
-
/**
|
|
510
|
-
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
511
|
-
*
|
|
512
|
-
* @example
|
|
513
|
-
* ```ts
|
|
514
|
-
* isValidVarName('status') // true
|
|
515
|
-
* isValidVarName('class') // false (reserved word)
|
|
516
|
-
* isValidVarName('42foo') // false (starts with digit)
|
|
517
|
-
* ```
|
|
518
|
-
*/
|
|
519
|
-
function isValidVarName(name) {
|
|
520
|
-
if (!name || reservedWords.has(name)) return false;
|
|
521
|
-
return isIdentifier(name);
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
525
|
-
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
526
|
-
*
|
|
527
|
-
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
528
|
-
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
529
|
-
* variable, type, or function names.
|
|
530
|
-
*
|
|
531
|
-
* @example
|
|
532
|
-
* ```ts
|
|
533
|
-
* ensureValidVarName('409') // '_409'
|
|
534
|
-
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
535
|
-
* ensureValidVarName('Pet') // 'Pet'
|
|
536
|
-
* ensureValidVarName('class') // '_class'
|
|
537
|
-
* ```
|
|
538
|
-
*/
|
|
539
|
-
function ensureValidVarName(name) {
|
|
540
|
-
if (!name || isValidVarName(name)) return name;
|
|
541
|
-
return `_${name}`;
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
545
|
-
*
|
|
546
|
-
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
547
|
-
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
548
|
-
* deciding whether an object key needs quoting.
|
|
549
|
-
*
|
|
550
|
-
* @example
|
|
551
|
-
* ```ts
|
|
552
|
-
* isIdentifier('name') // true
|
|
553
|
-
* isIdentifier('x-total')// false
|
|
554
|
-
* ```
|
|
555
|
-
*/
|
|
556
|
-
function isIdentifier(name) {
|
|
557
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
558
|
-
}
|
|
559
|
-
// ../../internals/utils/src/strings.ts
|
|
560
|
-
/**
|
|
561
|
-
* Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
|
|
562
|
-
* any backslash or single quote in the content.
|
|
563
|
-
*
|
|
564
|
-
* @example
|
|
565
|
-
* ```ts
|
|
566
|
-
* singleQuote('foo') // "'foo'"
|
|
567
|
-
* singleQuote("o'clock") // "'o\\'clock'"
|
|
568
|
-
* ```
|
|
569
|
-
*/
|
|
570
|
-
function singleQuote(value) {
|
|
571
|
-
if (value === void 0 || value === null) return "''";
|
|
572
|
-
return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
|
|
576
|
-
* Returns the string unchanged when no balanced quote pair is found.
|
|
577
|
-
*
|
|
578
|
-
* @example
|
|
579
|
-
* ```ts
|
|
580
|
-
* trimQuotes('"hello"') // 'hello'
|
|
581
|
-
* trimQuotes('hello') // 'hello'
|
|
582
|
-
* ```
|
|
583
|
-
*/
|
|
584
|
-
function trimQuotes(text) {
|
|
585
|
-
if (text.length >= 2) {
|
|
586
|
-
const first = text[0];
|
|
587
|
-
const last = text[text.length - 1];
|
|
588
|
-
if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
|
|
589
|
-
}
|
|
590
|
-
return text;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
|
|
594
|
-
*
|
|
595
|
-
* Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
|
|
596
|
-
* code matches the repo style without a formatter.
|
|
597
|
-
*
|
|
598
|
-
* @example
|
|
599
|
-
* ```ts
|
|
600
|
-
* stringify('hello') // "'hello'"
|
|
601
|
-
* stringify('"hello"') // "'hello'"
|
|
602
|
-
* ```
|
|
603
|
-
*/
|
|
604
|
-
function stringify(value) {
|
|
605
|
-
if (value === void 0 || value === null) return "''";
|
|
606
|
-
return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
|
|
607
|
-
}
|
|
608
|
-
const NEEDS_UNICODE = /(?<!\\)(?:\\\\)*\\[pPu]\{|[\u{10000}-\u{10FFFF}]/u;
|
|
609
|
-
const KEPT_ESCAPE = /[A-Za-z0-9^$\\.*+?()[\]{}|/]/;
|
|
610
|
-
/**
|
|
611
|
-
* Drops the backslash from escapes that the `u` flag rejects: an escaped punctuation character that is
|
|
612
|
-
* not a syntax character, and `\-` outside a class. Both match the same character without the backslash.
|
|
613
|
-
*/
|
|
614
|
-
function dropIdentityEscapes(pattern) {
|
|
615
|
-
let inClass = false;
|
|
616
|
-
return pattern.replace(/\\(.)|[[\]]/gs, (match, escaped) => {
|
|
617
|
-
if (escaped === void 0) {
|
|
618
|
-
inClass = match === "[";
|
|
619
|
-
return match;
|
|
620
|
-
}
|
|
621
|
-
return KEPT_ESCAPE.test(escaped) || escaped === "-" && inClass ? match : escaped;
|
|
536
|
+
members: [contentType, ast.factory.createSchema({
|
|
537
|
+
type: "object",
|
|
538
|
+
primitive: "object",
|
|
539
|
+
properties: [ast.factory.createProperty({
|
|
540
|
+
name: "request",
|
|
541
|
+
required: true,
|
|
542
|
+
schema: contentType
|
|
543
|
+
})]
|
|
544
|
+
})]
|
|
545
|
+
})
|
|
546
|
+
})]);
|
|
547
|
+
})
|
|
622
548
|
});
|
|
623
549
|
}
|
|
624
550
|
/**
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
try {
|
|
631
|
-
return new RegExp(dropIdentityEscapes(pattern), `${flags ?? ""}u`);
|
|
632
|
-
} catch {
|
|
633
|
-
return new RegExp(pattern, flags);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
|
|
638
|
-
* Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
|
|
639
|
-
* Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
|
|
640
|
-
*
|
|
641
|
-
* @example
|
|
642
|
-
* ```ts
|
|
643
|
-
* toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
|
|
644
|
-
* toRegExpString('^(?im)foo', null) // '/^foo/im'
|
|
645
|
-
* ```
|
|
646
|
-
*/
|
|
647
|
-
function toRegExpString(text, func = "RegExp") {
|
|
648
|
-
const raw = trimQuotes(text);
|
|
649
|
-
const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
|
|
650
|
-
const replacementTarget = match?.[1] ?? "";
|
|
651
|
-
const matchedFlags = match?.[2];
|
|
652
|
-
const { source, flags } = toRegExp(raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, ""), matchedFlags);
|
|
653
|
-
if (func === null) return `/${source}/${flags}`;
|
|
654
|
-
return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
|
|
655
|
-
}
|
|
656
|
-
// ../../internals/utils/src/codegen.ts
|
|
657
|
-
const INDENT = " ";
|
|
658
|
-
/**
|
|
659
|
-
* Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
|
|
551
|
+
* The schema a status occupies in the `<Name>Responses` record. A status that documents several
|
|
552
|
+
* content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
|
|
553
|
+
* on `result.parsed`, while the standalone `<Name>StatusNNN` alias stays the plain body union that the
|
|
554
|
+
* query hooks and `result.data` use. With `streamData`, the stream variant of a 2xx status holds that schema, since the
|
|
555
|
+
* client returns its body unread.
|
|
660
556
|
*/
|
|
661
|
-
function
|
|
662
|
-
|
|
663
|
-
|
|
557
|
+
function buildResponseRecordEntry(node, res, resolver, streamData) {
|
|
558
|
+
const statusName = resolver.response.status(node, res.statusCode);
|
|
559
|
+
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
560
|
+
if (variants.length <= 1) return ast.factory.createSchema({
|
|
561
|
+
type: "ref",
|
|
562
|
+
name: statusName
|
|
563
|
+
});
|
|
564
|
+
return ast.factory.createSchema({
|
|
565
|
+
type: "union",
|
|
566
|
+
members: resolveContentTypeVariants(variants, statusName).map((variant) => ast.factory.createSchema({
|
|
567
|
+
type: "object",
|
|
568
|
+
primitive: "object",
|
|
569
|
+
properties: [ast.factory.createProperty({
|
|
570
|
+
name: "contentType",
|
|
571
|
+
required: true,
|
|
572
|
+
schema: ast.factory.createSchema({
|
|
573
|
+
type: "enum",
|
|
574
|
+
enumValues: [ast.mediaType.getBase(variant)]
|
|
575
|
+
})
|
|
576
|
+
}), ast.factory.createProperty({
|
|
577
|
+
name: "data",
|
|
578
|
+
required: true,
|
|
579
|
+
schema: streamData && ast.http.status.isSuccess(res) && ast.mediaType.isStream(variant) ? streamData : ast.factory.createSchema({
|
|
580
|
+
type: "ref",
|
|
581
|
+
name: variant.name
|
|
582
|
+
})
|
|
583
|
+
})]
|
|
584
|
+
}))
|
|
585
|
+
});
|
|
664
586
|
}
|
|
665
587
|
/**
|
|
666
|
-
*
|
|
667
|
-
*
|
|
588
|
+
* Builds the per-status `<Name>Responses` record for an operation, referencing the already-resolved
|
|
589
|
+
* `<Name>StatusNNN` names. Shared by `@kubb/plugin-ts`'s `Responses` type and `@kubb/plugin-zod`'s
|
|
590
|
+
* inferred responses schema, so both emit the same shape from the same inputs.
|
|
668
591
|
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
* objectKey('x-total') // "'x-total'"
|
|
673
|
-
* ```
|
|
674
|
-
*/
|
|
675
|
-
function objectKey(name) {
|
|
676
|
-
return isIdentifier(name) ? name : singleQuote(name);
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
|
|
680
|
-
* level and closing the brace at column zero. Entries that are themselves multi-line objects indent
|
|
681
|
-
* cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
|
|
592
|
+
* Always emits the keyed record, even when an operation declares no responses. An operation with no
|
|
593
|
+
* responses renders as an empty object, which keeps every consumer's import (for example the axios
|
|
594
|
+
* SDK's `RequestResult<XResponses>`) resolvable instead of pointing at a missing export.
|
|
682
595
|
*
|
|
683
|
-
*
|
|
684
|
-
* ```ts
|
|
685
|
-
* buildObject(['id: z.number()', 'name: z.string()'])
|
|
686
|
-
* // '{\n id: z.number(),\n name: z.string(),\n}'
|
|
687
|
-
* ```
|
|
596
|
+
* `streamData` is the type of a stream variant's `data`, such as a `ReadableStream<Uint8Array>` reference.
|
|
688
597
|
*/
|
|
689
|
-
function
|
|
690
|
-
|
|
691
|
-
|
|
598
|
+
function buildResponses(node, resolver, { streamData } = {}) {
|
|
599
|
+
return ast.factory.createSchema({
|
|
600
|
+
type: "object",
|
|
601
|
+
primitive: "object",
|
|
602
|
+
properties: node.responses.map((res) => ast.factory.createProperty({
|
|
603
|
+
name: String(res.statusCode),
|
|
604
|
+
required: true,
|
|
605
|
+
schema: buildResponseRecordEntry(node, res, resolver, streamData)
|
|
606
|
+
}))
|
|
607
|
+
});
|
|
692
608
|
}
|
|
609
|
+
// ../../internals/shared/src/adapter.ts
|
|
693
610
|
/**
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
* one level with a trailing comma and the closing bracket at column zero. Used for member lists such
|
|
697
|
-
* as `z.union([…])` and `z.array([…])`.
|
|
611
|
+
* Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
|
|
612
|
+
* so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
|
|
698
613
|
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
* buildList(['z.string()', 'z.number()'])
|
|
702
|
-
* // '[z.string(), z.number()]'
|
|
703
|
-
* ```
|
|
704
|
-
*/
|
|
705
|
-
function buildList(items, brackets = ["[", "]"]) {
|
|
706
|
-
const [open, close] = brackets;
|
|
707
|
-
if (items.length === 0) return `${open}${close}`;
|
|
708
|
-
if (!items.some((item) => item.includes("\n"))) return `${open}${items.join(", ")}${close}`;
|
|
709
|
-
return `${open}\n${items.map((item) => `${indentLines(item)},`).join("\n")}\n${close}`;
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Emits a lazy getter for a circular-ref property position, `get name() { return body }`. The key
|
|
713
|
-
* is quoted only when it is not a valid identifier. Used by the string printers to defer evaluation
|
|
714
|
-
* of a recursive schema until first access.
|
|
614
|
+
* Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
|
|
615
|
+
* clear, actionable error at the point of use.
|
|
715
616
|
*
|
|
716
617
|
* @example
|
|
717
618
|
* ```ts
|
|
718
|
-
*
|
|
719
|
-
* // "get parent() { return z.lazy(() => Pet) }"
|
|
619
|
+
* const { dateType } = getOasAdapter(ctx.adapter).options
|
|
720
620
|
* ```
|
|
721
621
|
*/
|
|
722
|
-
function
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
732
|
-
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
733
|
-
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
734
|
-
* absolute path, letting generated files escape the configured output directory.
|
|
735
|
-
*
|
|
736
|
-
* @example Nested path from a dotted name
|
|
737
|
-
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
738
|
-
*
|
|
739
|
-
* @example PascalCase the final segment
|
|
740
|
-
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
741
|
-
*
|
|
742
|
-
* @example Suffix applied to the final segment only
|
|
743
|
-
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
744
|
-
*/
|
|
745
|
-
function toFilePath(name, caseLast = camelCase) {
|
|
746
|
-
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
747
|
-
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
622
|
+
function getOasAdapter(adapter) {
|
|
623
|
+
if (adapter.name !== "oas") throw new Diagnostics.Error({
|
|
624
|
+
code: Diagnostics.code.adapterRequired,
|
|
625
|
+
severity: "error",
|
|
626
|
+
message: `Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}".`,
|
|
627
|
+
help: "Set `adapter: adapterOas()` in kubb.config.ts.",
|
|
628
|
+
location: { kind: "config" }
|
|
629
|
+
});
|
|
630
|
+
return adapter;
|
|
748
631
|
}
|
|
749
632
|
// ../../internals/shared/src/resolver.ts
|
|
750
633
|
/**
|
|
@@ -834,6 +717,13 @@ function createOperationResponseResolver() {
|
|
|
834
717
|
name
|
|
835
718
|
});
|
|
836
719
|
},
|
|
720
|
+
headers(node, statusCode) {
|
|
721
|
+
const name = this.name(`${node.operationId} Status ${statusCode} Headers`);
|
|
722
|
+
return this.resolveName({
|
|
723
|
+
key: operationKey(node, "response", "headers", statusCode),
|
|
724
|
+
name
|
|
725
|
+
});
|
|
726
|
+
},
|
|
837
727
|
body(node) {
|
|
838
728
|
const name = this.name(`${node.operationId} Body`);
|
|
839
729
|
return this.resolveName({
|
|
@@ -875,75 +765,7 @@ function createCasedFile(caseLast) {
|
|
|
875
765
|
* Names of the schemas in a circular reference chain, computed once per build.
|
|
876
766
|
*/
|
|
877
767
|
function getCircularNames(ctx) {
|
|
878
|
-
return ctx.memo("circularNames:set", () => ast.
|
|
879
|
-
}
|
|
880
|
-
// ../../internals/shared/src/group.ts
|
|
881
|
-
/**
|
|
882
|
-
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
883
|
-
* shared default naming so every plugin groups output consistently:
|
|
884
|
-
*
|
|
885
|
-
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
886
|
-
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
887
|
-
*
|
|
888
|
-
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
889
|
-
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
890
|
-
* per-plugin convention.
|
|
891
|
-
*
|
|
892
|
-
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
893
|
-
*
|
|
894
|
-
* @example
|
|
895
|
-
* ```ts
|
|
896
|
-
* createGroupConfig(group) // shared across every plugin
|
|
897
|
-
* ```
|
|
898
|
-
*/
|
|
899
|
-
function createGroupConfig(group) {
|
|
900
|
-
if (!group) return null;
|
|
901
|
-
const defaultName = (ctx) => {
|
|
902
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
903
|
-
return camelCase(ctx.group);
|
|
904
|
-
};
|
|
905
|
-
return {
|
|
906
|
-
...group,
|
|
907
|
-
name: group.name ? group.name : defaultName
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
// ../../internals/shared/src/schemaTraversal.ts
|
|
911
|
-
/**
|
|
912
|
-
* Maps each property of an object schema to its transformed output. Pairs every result with the
|
|
913
|
-
* original property so the printer keeps full control over modifiers, getters, and key syntax.
|
|
914
|
-
*
|
|
915
|
-
* @example
|
|
916
|
-
* ```ts
|
|
917
|
-
* const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
|
|
918
|
-
* // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
|
|
919
|
-
* ```
|
|
920
|
-
*/
|
|
921
|
-
function mapSchemaProperties(node, transform) {
|
|
922
|
-
return node.properties.map((property) => ({
|
|
923
|
-
name: property.name,
|
|
924
|
-
property,
|
|
925
|
-
output: transform(property.schema)
|
|
926
|
-
}));
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Maps each member of a union or intersection schema to its transformed output, pairing every
|
|
930
|
-
* result with the original member.
|
|
931
|
-
*/
|
|
932
|
-
function mapSchemaMembers(node, transform) {
|
|
933
|
-
return (node.members ?? []).map((schema) => ({
|
|
934
|
-
schema,
|
|
935
|
-
output: transform(schema)
|
|
936
|
-
}));
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Maps each item of an array or tuple schema to its transformed output, pairing every result with
|
|
940
|
-
* the original item.
|
|
941
|
-
*/
|
|
942
|
-
function mapSchemaItems(node, transform) {
|
|
943
|
-
return (node.items ?? []).map((schema) => ({
|
|
944
|
-
schema,
|
|
945
|
-
output: transform(schema)
|
|
946
|
-
}));
|
|
768
|
+
return ctx.memo("circularNames:set", () => ast.graph.getCircularNames(ctx.driver.inputNode?.schemas ?? []));
|
|
947
769
|
}
|
|
948
770
|
// src/components/Zod.tsx
|
|
949
771
|
function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuards, isName, assertName, mini, cyclic, compile }) {
|
|
@@ -951,9 +773,9 @@ function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuar
|
|
|
951
773
|
const printerImports = printer.drainImports();
|
|
952
774
|
if (!output) return;
|
|
953
775
|
const needsAnnotation = cyclic && node.type !== "object";
|
|
954
|
-
const
|
|
955
|
-
const isCyclic = Boolean(cyclic || printer.options.cyclicSchemas &&
|
|
956
|
-
const value = Boolean(compile) && !
|
|
776
|
+
const isAlias = ast.ref.isAlias(node, printer.options.keysToOmit);
|
|
777
|
+
const isCyclic = Boolean(cyclic || printer.options.cyclicSchemas && ast.graph.containsCircular(node, { circularSchemas: printer.options.cyclicSchemas }));
|
|
778
|
+
const value = Boolean(compile) && !isAlias && !isCyclic ? typeof compile === "object" && compile.strict ? `z.compile(${output}, { strict: true })` : `z.compile(${output})` : output;
|
|
957
779
|
const targetType = inferTypeName ?? `z.infer<typeof ${name}>`;
|
|
958
780
|
const shouldGenerateIs = isName && (typeof typeGuards === "object" ? typeGuards.is ?? true : Boolean(typeGuards));
|
|
959
781
|
const shouldGenerateAssert = assertName && (typeof typeGuards === "object" ? typeGuards.assert ?? true : Boolean(typeGuards));
|
|
@@ -976,7 +798,7 @@ function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuar
|
|
|
976
798
|
children: /* @__PURE__ */ jsx(Const, {
|
|
977
799
|
export: true,
|
|
978
800
|
name,
|
|
979
|
-
type: needsAnnotation ? "z.ZodType" : void 0,
|
|
801
|
+
type: needsAnnotation ? mini ? "z.ZodMiniType" : "z.ZodType" : void 0,
|
|
980
802
|
children: value
|
|
981
803
|
})
|
|
982
804
|
}),
|
|
@@ -1036,7 +858,7 @@ function shouldCoerce(coercion, type) {
|
|
|
1036
858
|
*/
|
|
1037
859
|
function isClosedKeySchema(node) {
|
|
1038
860
|
if (!node) return false;
|
|
1039
|
-
const resolved =
|
|
861
|
+
const resolved = ast.ref.mergeSchema(node);
|
|
1040
862
|
if (resolved.type === "enum") return true;
|
|
1041
863
|
if (resolved.type === "union") {
|
|
1042
864
|
const members = resolved.members ?? [];
|
|
@@ -1046,12 +868,12 @@ function isClosedKeySchema(node) {
|
|
|
1046
868
|
}
|
|
1047
869
|
/**
|
|
1048
870
|
* Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
|
|
1049
|
-
* argument.
|
|
871
|
+
* argument. Input checks, a catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
|
|
1050
872
|
* on `.and(…)`.
|
|
1051
873
|
*/
|
|
1052
874
|
function isPlainInlineObject(node) {
|
|
1053
875
|
if (node.type !== "object") return false;
|
|
1054
|
-
if (node
|
|
876
|
+
if (hasInputChecks(node)) return false;
|
|
1055
877
|
if (node.nullable || node.optional || node.nullish) return false;
|
|
1056
878
|
const hasAdditionalProperties = node.additionalProperties !== void 0;
|
|
1057
879
|
const hasPatternProperties = Boolean(node.patternProperties);
|
|
@@ -1062,21 +884,21 @@ function isPlainInlineObject(node) {
|
|
|
1062
884
|
* `z.discriminatedUnion` option. Covers object nodes, `$ref`s that resolve to (or are still
|
|
1063
885
|
* unresolved) objects, single-member unions of an object, and object-composable `allOf`.
|
|
1064
886
|
*
|
|
1065
|
-
*
|
|
887
|
+
* Input checks, such as property counts and `conditionals`, render as a pipe. A ref into `cyclicSchemas` renders as `z.lazy(…)`.
|
|
1066
888
|
*/
|
|
1067
889
|
function isObjectSchemaNode(node, cyclicSchemas) {
|
|
1068
890
|
if (node.nullable || node.optional || node.nullish) return false;
|
|
891
|
+
if (hasInputChecks(node)) return false;
|
|
1069
892
|
if (node.type === "object") {
|
|
1070
|
-
if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
|
|
1071
893
|
const hasProperties = Boolean(node.properties?.length);
|
|
1072
894
|
const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
|
|
1073
895
|
const hasPatternProperties = Boolean(node.patternProperties);
|
|
1074
896
|
return !(!hasProperties && (hasTypedAdditionalProperties || hasPatternProperties || node.propertyNames));
|
|
1075
897
|
}
|
|
1076
898
|
if (node.type === "ref") {
|
|
1077
|
-
const refName = ast.
|
|
899
|
+
const refName = ast.ref.getName(node);
|
|
1078
900
|
if (refName && cyclicSchemas?.has(refName)) return false;
|
|
1079
|
-
const resolved =
|
|
901
|
+
const resolved = ast.ref.mergeSchema(node);
|
|
1080
902
|
return resolved.type === "ref" || isObjectSchemaNode(resolved, cyclicSchemas);
|
|
1081
903
|
}
|
|
1082
904
|
if (node.type === "union") {
|
|
@@ -1099,23 +921,23 @@ function preserveRequiredProperties(members) {
|
|
|
1099
921
|
const hasRequiredProperty = ({ node, propertyName, seen = /* @__PURE__ */ new Set() }) => {
|
|
1100
922
|
if (seen.has(node)) return false;
|
|
1101
923
|
seen.add(node);
|
|
1102
|
-
const resolved =
|
|
1103
|
-
const composition = ast.
|
|
924
|
+
const resolved = ast.ref.mergeSchema(node);
|
|
925
|
+
const composition = ast.schema.narrow(resolved, ast.schema.types.intersection) ?? ast.schema.narrow(resolved, ast.schema.types.union);
|
|
1104
926
|
if (composition) {
|
|
1105
|
-
if (composition.type === ast.
|
|
927
|
+
if (composition.type === ast.schema.types.union && composition.members?.length !== 1) return false;
|
|
1106
928
|
return composition.members?.some((child) => hasRequiredProperty({
|
|
1107
929
|
node: child,
|
|
1108
930
|
propertyName,
|
|
1109
931
|
seen
|
|
1110
932
|
})) ?? false;
|
|
1111
933
|
}
|
|
1112
|
-
return ast.
|
|
934
|
+
return ast.schema.findProperties({
|
|
1113
935
|
node: resolved,
|
|
1114
936
|
propertyName
|
|
1115
937
|
}).some((property) => property.required !== false && !property.schema.optional && !property.schema.nullish);
|
|
1116
938
|
};
|
|
1117
939
|
return members.map((member, index) => {
|
|
1118
|
-
const object = ast.
|
|
940
|
+
const object = ast.schema.narrow(member, ast.schema.types.object);
|
|
1119
941
|
if (!object || index === 0) return member;
|
|
1120
942
|
return {
|
|
1121
943
|
...object,
|
|
@@ -1144,7 +966,7 @@ function preserveRequiredProperties(members) {
|
|
|
1144
966
|
* representation, strings are quoted.
|
|
1145
967
|
*/
|
|
1146
968
|
function formatDefault(value) {
|
|
1147
|
-
if (typeof value === "string") return
|
|
969
|
+
if (typeof value === "string") return text.quote(value);
|
|
1148
970
|
if (typeof value === "object" && value !== null) return JSON.stringify(value);
|
|
1149
971
|
return String(value ?? "");
|
|
1150
972
|
}
|
|
@@ -1159,16 +981,17 @@ function formatDefault(value) {
|
|
|
1159
981
|
*/
|
|
1160
982
|
function defaultLiteral(node, value) {
|
|
1161
983
|
if (value === null) return null;
|
|
1162
|
-
const resolved = node ?
|
|
1163
|
-
if (resolved
|
|
984
|
+
const resolved = node ? ast.ref.mergeSchema(node) : void 0;
|
|
985
|
+
if (resolved?.type === "blob") return null;
|
|
986
|
+
if (resolved && ast.schema.narrow(resolved, "bigint")) {
|
|
1164
987
|
if (typeof value === "bigint") return `BigInt(${value})`;
|
|
1165
988
|
if (typeof value === "number" && Number.isInteger(value)) return `BigInt(${value})`;
|
|
1166
989
|
return null;
|
|
1167
990
|
}
|
|
1168
|
-
if (resolved && ast.
|
|
1169
|
-
const enumNode = resolved ? ast.
|
|
991
|
+
if (resolved && ast.schema.narrow(resolved, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
|
|
992
|
+
const enumNode = resolved ? ast.schema.narrow(resolved, "enum") : void 0;
|
|
1170
993
|
if (enumNode) {
|
|
1171
|
-
const values =
|
|
994
|
+
const values = ast.schema.getLiterals(enumNode);
|
|
1172
995
|
if (values.length) {
|
|
1173
996
|
const match = values.find((member) => member === value || String(member) === String(value));
|
|
1174
997
|
return match != null ? formatDefault(match) : null;
|
|
@@ -1237,13 +1060,13 @@ function patternKeySchemaMini({ patterns, regexType }) {
|
|
|
1237
1060
|
* Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
|
|
1238
1061
|
* using the standard chainable Zod v4 API.
|
|
1239
1062
|
*/
|
|
1240
|
-
function numberConstraints({ min, max,
|
|
1063
|
+
function numberConstraints({ min, max, exclusive, multipleOf }, suffix = "") {
|
|
1241
1064
|
return [
|
|
1242
|
-
min !== void 0 ? `.min(${min})` : "",
|
|
1243
|
-
max !== void 0 ? `.max(${max})` : "",
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
multipleOf !== void 0 ? `.multipleOf(${multipleOf})` : ""
|
|
1065
|
+
min !== void 0 ? `.min(${min}${suffix})` : "",
|
|
1066
|
+
max !== void 0 ? `.max(${max}${suffix})` : "",
|
|
1067
|
+
exclusive?.min !== void 0 ? `.gt(${exclusive.min}${suffix})` : "",
|
|
1068
|
+
exclusive?.max !== void 0 ? `.lt(${exclusive.max}${suffix})` : "",
|
|
1069
|
+
multipleOf !== void 0 ? `.multipleOf(${multipleOf}${suffix})` : ""
|
|
1247
1070
|
].join("");
|
|
1248
1071
|
}
|
|
1249
1072
|
/**
|
|
@@ -1258,36 +1081,71 @@ function lengthConstraints({ min, max, pattern, regexType }) {
|
|
|
1258
1081
|
].join("");
|
|
1259
1082
|
}
|
|
1260
1083
|
/**
|
|
1084
|
+
* Numeric bounds for a bigint: the inclusive, whole, safe bounds faker mocks against, and a whole, safe `multipleOf`.
|
|
1085
|
+
*/
|
|
1086
|
+
function bigintConstraints({ min, max, exclusive, multipleOf }) {
|
|
1087
|
+
return {
|
|
1088
|
+
...ast.schema.getNumberBounds({
|
|
1089
|
+
min,
|
|
1090
|
+
max,
|
|
1091
|
+
exclusive,
|
|
1092
|
+
integer: true
|
|
1093
|
+
}),
|
|
1094
|
+
multipleOf: Number.isSafeInteger(multipleOf) ? multipleOf : void 0
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1261
1098
|
* Build `.check(z.minimum(), z.maximum())` for `zod/mini` numeric constraints.
|
|
1262
1099
|
*/
|
|
1263
|
-
function numberChecksMini({ min, max,
|
|
1264
|
-
const checks = [
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1100
|
+
function numberChecksMini({ min, max, exclusive, multipleOf }, suffix = "") {
|
|
1101
|
+
const checks = [
|
|
1102
|
+
min !== void 0 && `z.minimum(${min}${suffix})`,
|
|
1103
|
+
max !== void 0 && `z.maximum(${max}${suffix})`,
|
|
1104
|
+
exclusive?.min !== void 0 && `z.gt(${exclusive.min}${suffix})`,
|
|
1105
|
+
exclusive?.max !== void 0 && `z.lt(${exclusive.max}${suffix})`,
|
|
1106
|
+
multipleOf !== void 0 && `z.multipleOf(${multipleOf}${suffix})`
|
|
1107
|
+
].filter(Boolean);
|
|
1270
1108
|
return checks.length ? `.check(${checks.join(", ")})` : "";
|
|
1271
1109
|
}
|
|
1272
1110
|
/**
|
|
1273
1111
|
* Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
|
|
1274
1112
|
*/
|
|
1275
1113
|
function lengthChecksMini({ min, max, pattern, regexType }) {
|
|
1276
|
-
const checks = [
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1114
|
+
const checks = [
|
|
1115
|
+
min !== void 0 && `z.minLength(${min})`,
|
|
1116
|
+
max !== void 0 && `z.maxLength(${max})`,
|
|
1117
|
+
pattern !== void 0 && `z.regex(${regexSource(pattern, regexType)})`
|
|
1118
|
+
].filter(Boolean);
|
|
1280
1119
|
return checks.length ? `.check(${checks.join(", ")})` : "";
|
|
1281
1120
|
}
|
|
1282
1121
|
/**
|
|
1283
|
-
*
|
|
1122
|
+
* Whether `propertyNames` needs its own check: `z.record` checks the keys of a map, but `z.object` and a record keyed by
|
|
1123
|
+
* `patternProperties` do not.
|
|
1124
|
+
*/
|
|
1125
|
+
function hasKeyCheck(node) {
|
|
1126
|
+
if (node.type !== "object" || !node.propertyNames) return false;
|
|
1127
|
+
const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
|
|
1128
|
+
return node.properties.length > 0 || Boolean(node.patternProperties) && !hasTypedAdditionalProperties;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Whether the node prints inside `z.pipe(z.unknown().check(…), …)`, see {@link inputChecks}.
|
|
1284
1132
|
*/
|
|
1285
|
-
function
|
|
1286
|
-
|
|
1287
|
-
|
|
1133
|
+
function hasInputChecks(node) {
|
|
1134
|
+
return Boolean(node.conditionals?.length) || node.type === "object" && (node.min !== void 0 || node.max !== void 0) || hasKeyCheck(node);
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Checks the raw input, before parsing strips unknown keys or inserts defaults: property counts, key names, then the
|
|
1138
|
+
* printed `conditionals`.
|
|
1139
|
+
*/
|
|
1140
|
+
function inputChecks({ value, node, keySchema, conditionals }) {
|
|
1288
1141
|
const skip = "typeof value !== \"object\" || value === null || Array.isArray(value)";
|
|
1289
|
-
|
|
1290
|
-
|
|
1142
|
+
const { min, max } = node.type === "object" ? node : {};
|
|
1143
|
+
const checks = [
|
|
1144
|
+
min !== void 0 && `z.refine(value => ${skip} || Object.keys(value).length >= ${min}, { message: "Object must have at least ${min} properties" })`,
|
|
1145
|
+
max !== void 0 && `z.refine(value => ${skip} || Object.keys(value).length <= ${max}, { message: "Object must have at most ${max} properties" })`,
|
|
1146
|
+
keySchema && `z.refine(value => ${skip} || Object.keys(value).every((key) => ${keySchema}.safeParse(key).success), { message: "Object keys must match the property names schema" })`,
|
|
1147
|
+
...conditionals
|
|
1148
|
+
].filter(Boolean);
|
|
1291
1149
|
return checks.length ? `z.pipe(z.unknown().check(${checks.join(", ")}), ${value})` : value;
|
|
1292
1150
|
}
|
|
1293
1151
|
/**
|
|
@@ -1303,8 +1161,8 @@ function applyModifiers({ value, schema, nullable, optional, nullish, defaultVal
|
|
|
1303
1161
|
})();
|
|
1304
1162
|
const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
|
|
1305
1163
|
const withDefault = literal !== null ? `${withModifier}.default(${literal})` : withModifier;
|
|
1306
|
-
const withDescription = description ? `${withDefault}.describe(${
|
|
1307
|
-
return examples?.length ? `${withDescription}.meta({ examples: [${examples.map(formatDefault).join(", ")}] })` : withDescription;
|
|
1164
|
+
const withDescription = description ? `${withDefault}.describe(${text.quote(description)})` : withDefault;
|
|
1165
|
+
return examples?.length ? `${withDescription}.meta({ examples: [${examples.map((example) => example === null ? "null" : formatDefault(example)).join(", ")}] })` : withDescription;
|
|
1308
1166
|
}
|
|
1309
1167
|
function modifierDepth(schema) {
|
|
1310
1168
|
if (schema.nullish || schema.nullable && schema.optional) return 2;
|
|
@@ -1322,7 +1180,7 @@ function modifierDepth(schema) {
|
|
|
1322
1180
|
* `.unwrap()`, so its printer passes `._zod.def.innerType` as the `step`.
|
|
1323
1181
|
*/
|
|
1324
1182
|
function omitUnwrapChain(node, step = ".unwrap()") {
|
|
1325
|
-
const ref = ast.
|
|
1183
|
+
const ref = ast.schema.narrow(node, "ref");
|
|
1326
1184
|
if (!ref) return "";
|
|
1327
1185
|
const target = ref.schema ?? ref;
|
|
1328
1186
|
const depth = modifierDepth(target) + (target.default !== void 0 ? 1 : 0);
|
|
@@ -1344,7 +1202,7 @@ function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaul
|
|
|
1344
1202
|
/**
|
|
1345
1203
|
* Calls for string `format`s with a dedicated Zod constructor; a `Map` so `constructor` cannot hit `Object.prototype`.
|
|
1346
1204
|
*/
|
|
1347
|
-
const
|
|
1205
|
+
const stringFormatCalls = /* @__PURE__ */ new Map([
|
|
1348
1206
|
["byte", "z.base64()"],
|
|
1349
1207
|
["base64", "z.base64()"],
|
|
1350
1208
|
["base64url", "z.base64url()"],
|
|
@@ -1355,6 +1213,16 @@ const STRING_FORMAT_CALLS = /* @__PURE__ */ new Map([
|
|
|
1355
1213
|
["hostname", "z.hostname()"]
|
|
1356
1214
|
]);
|
|
1357
1215
|
/**
|
|
1216
|
+
* Calls for a string's `contentEncoding` with a dedicated Zod constructor.
|
|
1217
|
+
*/
|
|
1218
|
+
const contentEncodingCalls = /* @__PURE__ */ new Map([["base64", "z.base64()"], ["base64url", "z.base64url()"]]);
|
|
1219
|
+
/**
|
|
1220
|
+
* The check for a string that holds a JSON document: it must parse, and the parsed value must pass `schema`.
|
|
1221
|
+
*/
|
|
1222
|
+
function jsonContentCheck(schema) {
|
|
1223
|
+
return `(value) => { try { return ${schema}.safeParse(JSON.parse(value)).success } catch { return false } }, { message: "String must hold JSON that matches its content schema" }`;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1358
1226
|
* Marks an inline `oneOf` object strict, so `object()` builds `z.strictObject(...)` and no eager `.shape` read hits a deferred getter.
|
|
1359
1227
|
*/
|
|
1360
1228
|
function strictOneOfNode(node) {
|
|
@@ -1367,13 +1235,14 @@ function strictOneOfNode(node) {
|
|
|
1367
1235
|
* The constraint chain an `allOf` member adds to a bare string, number or array; each printer passes its own renderers.
|
|
1368
1236
|
*/
|
|
1369
1237
|
function getMemberConstraint({ member, regexType, lengths, numbers }) {
|
|
1238
|
+
if (hasInputChecks(member) || ast.schema.narrow(member, "array")?.contains) return void 0;
|
|
1370
1239
|
if (member.primitive === "string") return lengths({
|
|
1371
|
-
...ast.
|
|
1240
|
+
...ast.schema.narrow(member, "string") ?? {},
|
|
1372
1241
|
regexType
|
|
1373
1242
|
}) || void 0;
|
|
1374
|
-
if (member.primitive === "number" || member.primitive === "integer") return numbers(ast.
|
|
1243
|
+
if (member.primitive === "number" || member.primitive === "integer") return numbers(ast.schema.narrow(member, "number") ?? ast.schema.narrow(member, "integer") ?? {}) || void 0;
|
|
1375
1244
|
if (member.primitive === "array") return lengths({
|
|
1376
|
-
...ast.
|
|
1245
|
+
...ast.schema.narrow(member, "array") ?? {},
|
|
1377
1246
|
regexType
|
|
1378
1247
|
}) || void 0;
|
|
1379
1248
|
}
|
|
@@ -1381,24 +1250,26 @@ function getMemberConstraint({ member, regexType, lengths, numbers }) {
|
|
|
1381
1250
|
* Builds the `{ key: value, … }` shape shared by the `z.object(...)` and `.extend(...)` renderings.
|
|
1382
1251
|
*/
|
|
1383
1252
|
function buildObjectShape(ctx, node, renderProperty) {
|
|
1384
|
-
const objectNode = ast.
|
|
1253
|
+
const objectNode = ast.schema.narrow(node, "object");
|
|
1385
1254
|
if (!objectNode) return "{}";
|
|
1386
|
-
const isCyclic = (schema) => ctx.options.cyclicSchemas != null &&
|
|
1387
|
-
|
|
1255
|
+
const isCyclic = (schema) => ctx.options.cyclicSchemas != null && ast.graph.containsCircular(schema, { circularSchemas: ctx.options.cyclicSchemas });
|
|
1256
|
+
const transformed = objectNode.properties.map(({ schema }) => {
|
|
1388
1257
|
const hasSelfRef = isCyclic(schema);
|
|
1389
1258
|
const savedCyclicSchemas = ctx.options.cyclicSchemas;
|
|
1390
1259
|
if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
|
|
1391
1260
|
const baseOutput = ctx.transform(schema) ?? ctx.transform(ast.factory.createSchema({ type: "unknown" }));
|
|
1392
1261
|
if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
|
|
1393
1262
|
return baseOutput;
|
|
1394
|
-
})
|
|
1395
|
-
|
|
1263
|
+
});
|
|
1264
|
+
return buildObject(objectNode.properties.map((property, index) => {
|
|
1265
|
+
const { name, schema } = property;
|
|
1266
|
+
const value = transformed[index];
|
|
1396
1267
|
return renderProperty({
|
|
1397
1268
|
name,
|
|
1398
1269
|
value,
|
|
1399
1270
|
schema,
|
|
1400
1271
|
required: property.required,
|
|
1401
|
-
meta:
|
|
1272
|
+
meta: ast.ref.mergeSchema(schema),
|
|
1402
1273
|
cyclic: isCyclic(schema)
|
|
1403
1274
|
});
|
|
1404
1275
|
}));
|
|
@@ -1410,14 +1281,14 @@ function printWithOmit(ctx, node, modify, mini = false) {
|
|
|
1410
1281
|
const { keysToOmit } = ctx.options;
|
|
1411
1282
|
const transformed = ctx.transform(node);
|
|
1412
1283
|
if (!transformed) return null;
|
|
1413
|
-
const meta =
|
|
1284
|
+
const meta = ast.ref.mergeSchema(node);
|
|
1414
1285
|
return modify((() => {
|
|
1415
1286
|
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
1416
1287
|
const unwrap = omitUnwrapChain(node, mini ? "._zod.def.innerType" : ".unwrap()");
|
|
1417
1288
|
const mask = `{ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }`;
|
|
1418
1289
|
const omit = (target) => {
|
|
1419
1290
|
const inner = `${target}${unwrap}`;
|
|
1420
|
-
const hasObjectConstraints =
|
|
1291
|
+
const hasObjectConstraints = hasInputChecks(meta);
|
|
1421
1292
|
const pipe = mini ? `${inner}._zod.def` : inner;
|
|
1422
1293
|
const object = hasObjectConstraints ? `${pipe}.out` : inner;
|
|
1423
1294
|
const omitted = mini ? `z.omit(${object}, ${mask})` : `${object}.omit(${mask})`;
|
|
@@ -1436,7 +1307,7 @@ function printWithOmit(ctx, node, modify, mini = false) {
|
|
|
1436
1307
|
function getLiteralUnion({ node, options }) {
|
|
1437
1308
|
if (node.strategy === "one" && options.oneOfType === "xor" || options.nodes?.enum) return void 0;
|
|
1438
1309
|
const values = (node.members ?? []).map((member) => {
|
|
1439
|
-
const enumNode = ast.
|
|
1310
|
+
const enumNode = ast.schema.narrow(member, ast.schema.types.enum);
|
|
1440
1311
|
return enumNode && !enumNode.name && !enumNode.nullable && !enumNode.namedEnumValues && enumNode.enumValues?.length === 1 ? enumNode.enumValues[0] : void 0;
|
|
1441
1312
|
});
|
|
1442
1313
|
if (!values.length || !values.every((value) => value !== void 0 && value !== null)) return void 0;
|
|
@@ -1449,19 +1320,26 @@ function getLiteralUnion({ node, options }) {
|
|
|
1449
1320
|
* Whether a discriminated union member's discriminator property prints as a coerced literal union.
|
|
1450
1321
|
*/
|
|
1451
1322
|
function hasCoercedDiscriminator({ member, propertyName, options }) {
|
|
1452
|
-
return ast.
|
|
1323
|
+
return ast.schema.findProperties({
|
|
1453
1324
|
node: member,
|
|
1454
1325
|
propertyName
|
|
1455
1326
|
}).some(({ schema }) => {
|
|
1456
|
-
const resolved =
|
|
1327
|
+
const resolved = ast.ref.mergeSchema(schema);
|
|
1457
1328
|
return resolved.type === "union" && !!getLiteralUnion({
|
|
1458
1329
|
node: resolved,
|
|
1459
1330
|
options
|
|
1460
1331
|
})?.coerce;
|
|
1461
1332
|
});
|
|
1462
1333
|
}
|
|
1334
|
+
function hasPlainDiscriminator({ member, propertyName }) {
|
|
1335
|
+
const schemas = ast.schema.findProperties({
|
|
1336
|
+
node: member,
|
|
1337
|
+
propertyName
|
|
1338
|
+
});
|
|
1339
|
+
return schemas.length > 0 && !schemas.some(({ schema }) => ast.schema.getLiterals(ast.ref.mergeSchema(schema)).length > 0);
|
|
1340
|
+
}
|
|
1463
1341
|
/**
|
|
1464
|
-
* Builds the scalar handlers both dialects share.
|
|
1342
|
+
* Builds the scalar handlers both dialects share. Only a string with a `contentSchema` recurses into a child. The temporal
|
|
1465
1343
|
* scalars and `bigint` differ per dialect, so each printer adds its own.
|
|
1466
1344
|
*/
|
|
1467
1345
|
function buildScalarNodes(dialect) {
|
|
@@ -1474,18 +1352,20 @@ function buildScalarNodes(dialect) {
|
|
|
1474
1352
|
null: () => "z.null()",
|
|
1475
1353
|
string(node) {
|
|
1476
1354
|
const coerce = shouldCoerce(this.options.coercion, "strings");
|
|
1477
|
-
const
|
|
1478
|
-
if (call) return `${call}${dialect.lengths({
|
|
1479
|
-
...node,
|
|
1480
|
-
regexType: this.options.regexType
|
|
1481
|
-
})}`;
|
|
1482
|
-
const base = coerce ? "z.coerce.string()" : "z.string()";
|
|
1355
|
+
const base = (coerce ? void 0 : stringFormatCalls.get(node.format) ?? contentEncodingCalls.get(node.content?.encoding?.toLowerCase())) ?? (coerce ? "z.coerce.string()" : "z.string()");
|
|
1483
1356
|
const pattern = node.pattern ?? integerFormatPattern(node.format);
|
|
1484
|
-
|
|
1357
|
+
const value = `${base}${dialect.lengths({
|
|
1485
1358
|
...node,
|
|
1486
1359
|
pattern,
|
|
1487
1360
|
regexType: this.options.regexType
|
|
1488
1361
|
})}`;
|
|
1362
|
+
const contentNode = ast.schema.getEmbeddedJson(node);
|
|
1363
|
+
if (!contentNode) return value;
|
|
1364
|
+
const direction = this.options.direction;
|
|
1365
|
+
this.options.direction = "decode";
|
|
1366
|
+
const contentSchema = this.transform(contentNode);
|
|
1367
|
+
this.options.direction = direction;
|
|
1368
|
+
return contentSchema ? dialect.refine(value, jsonContentCheck(contentSchema)) : value;
|
|
1489
1369
|
},
|
|
1490
1370
|
number(node) {
|
|
1491
1371
|
return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${dialect.numbers(node)}`;
|
|
@@ -1518,7 +1398,7 @@ function buildScalarNodes(dialect) {
|
|
|
1518
1398
|
ipv6: () => "z.ipv6()",
|
|
1519
1399
|
blob: () => "z.instanceof(Blob)",
|
|
1520
1400
|
enum(node) {
|
|
1521
|
-
const nonNullValues =
|
|
1401
|
+
const nonNullValues = ast.schema.getLiterals(node).filter((v) => v !== null);
|
|
1522
1402
|
if (node.namedEnumValues?.length) {
|
|
1523
1403
|
const literals = nonNullValues.map((v) => buildLiteral(v));
|
|
1524
1404
|
if (literals.length === 1) return literals[0];
|
|
@@ -1537,11 +1417,12 @@ function buildScalarNodes(dialect) {
|
|
|
1537
1417
|
*/
|
|
1538
1418
|
function buildZodNodes(dialect, cyclicSchemas) {
|
|
1539
1419
|
const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
|
|
1420
|
+
const printNullable = (schema, output) => output && schema.nullable ? dialect.nullable(output) : output;
|
|
1540
1421
|
return {
|
|
1541
1422
|
...buildScalarNodes(dialect),
|
|
1542
1423
|
ref(node) {
|
|
1543
1424
|
if (!node.name) return null;
|
|
1544
|
-
const refName = ast.
|
|
1425
|
+
const refName = ast.ref.getName(node);
|
|
1545
1426
|
if (!refName) return null;
|
|
1546
1427
|
const useInputVariant = node.ref != null && (dialect.useInputVariant?.(node, this.options) ?? false);
|
|
1547
1428
|
const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.schema.inputName(refName) ?? refName : this.options.resolver?.name(refName) ?? refName : node.name;
|
|
@@ -1556,47 +1437,50 @@ function buildZodNodes(dialect, cyclicSchemas) {
|
|
|
1556
1437
|
const isStrict = node.additionalProperties === false && patterns.length === 0;
|
|
1557
1438
|
const isLoose = node.additionalProperties === true;
|
|
1558
1439
|
const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${dialect.objectShape(this, node)})`;
|
|
1559
|
-
return
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
})(),
|
|
1588
|
-
node
|
|
1589
|
-
});
|
|
1440
|
+
return (() => {
|
|
1441
|
+
const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
|
|
1442
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
1443
|
+
const catchallType = this.transform(node.additionalProperties);
|
|
1444
|
+
if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
|
|
1445
|
+
return catchallType ? dialect.catchall(objectBase, catchallType) : objectBase;
|
|
1446
|
+
}
|
|
1447
|
+
if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
|
|
1448
|
+
const unknownType = this.transform(ast.factory.createSchema({ type: ast.schema.types.unknown }));
|
|
1449
|
+
return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
|
|
1450
|
+
}
|
|
1451
|
+
if (node.additionalProperties === true) return objectBase;
|
|
1452
|
+
if (isStrict) return objectBase;
|
|
1453
|
+
if (patterns.length > 0) {
|
|
1454
|
+
const values = patterns.map(([, valueSchema]) => {
|
|
1455
|
+
const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: ast.schema.types.unknown }));
|
|
1456
|
+
return valueSchema.nullable ? dialect.nullable(valueType) : valueType;
|
|
1457
|
+
});
|
|
1458
|
+
const distinct = [...new Set(values)];
|
|
1459
|
+
const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
|
|
1460
|
+
if (entries.length > 0) return dialect.catchall(objectBase, value);
|
|
1461
|
+
return `z.record(${dialect.patternKey({
|
|
1462
|
+
patterns: patterns.map(([pattern]) => pattern),
|
|
1463
|
+
regexType: this.options.regexType
|
|
1464
|
+
})}, ${value})`;
|
|
1465
|
+
}
|
|
1466
|
+
return objectBase;
|
|
1467
|
+
})();
|
|
1590
1468
|
},
|
|
1591
1469
|
array(node) {
|
|
1592
|
-
const base = `z.array(${
|
|
1470
|
+
const base = `z.array(${(node.items ?? []).map((item) => printNullable(item, this.transform(item))).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: ast.schema.types.unknown }))})${dialect.lengths({
|
|
1593
1471
|
...node,
|
|
1594
1472
|
regexType: this.options.regexType
|
|
1595
1473
|
})}`;
|
|
1596
|
-
|
|
1474
|
+
const unique = node.unique ? dialect.unique(base) : base;
|
|
1475
|
+
const contains = node.contains && containsCheck(this, node.contains);
|
|
1476
|
+
return contains ? dialect.refine(unique, contains) : unique;
|
|
1597
1477
|
},
|
|
1598
1478
|
tuple(node) {
|
|
1599
|
-
|
|
1479
|
+
const items = (node.items ?? []).map((item) => printNullable(item, this.transform(item))).filter(Boolean);
|
|
1480
|
+
const rest = node.rest ? this.transform(node.rest) : void 0;
|
|
1481
|
+
const tuple = `z.tuple(${buildList(items)}${rest ? `, ${rest}` : ""})`;
|
|
1482
|
+
const contains = node.contains && containsCheck(this, node.contains);
|
|
1483
|
+
return contains ? dialect.refine(tuple, contains) : tuple;
|
|
1600
1484
|
},
|
|
1601
1485
|
union(node) {
|
|
1602
1486
|
const literalUnion = getLiteralUnion({
|
|
@@ -1609,24 +1493,30 @@ function buildZodNodes(dialect, cyclicSchemas) {
|
|
|
1609
1493
|
}
|
|
1610
1494
|
const nodeMembers = node.members ?? [];
|
|
1611
1495
|
const { discriminatorPropertyName } = node;
|
|
1612
|
-
const isDiscriminated = !!discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemas)
|
|
1496
|
+
const isDiscriminated = !!discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemas) && !hasPlainDiscriminator({
|
|
1497
|
+
member: m,
|
|
1498
|
+
propertyName: discriminatorPropertyName
|
|
1499
|
+
})) && !nodeMembers.some((m) => hasCoercedDiscriminator({
|
|
1613
1500
|
member: m,
|
|
1614
1501
|
propertyName: discriminatorPropertyName,
|
|
1615
1502
|
options: this.options
|
|
1616
1503
|
}));
|
|
1617
1504
|
const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
|
|
1618
|
-
const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
|
|
1619
|
-
const members =
|
|
1505
|
+
const isStrict = node.strategy === "one" && !isXor && !isDiscriminated && !unionsWithSiblings.has(node);
|
|
1506
|
+
const members = (node.members ?? []).map((memberNode) => {
|
|
1507
|
+
const output = this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode);
|
|
1508
|
+
return output && isStrict && dialect.strictMember ? dialect.strictMember(output, memberNode, cyclicSchemas) : output;
|
|
1509
|
+
}).map((output, index) => printNullable(nodeMembers[index], output)).filter(Boolean);
|
|
1620
1510
|
if (members.length === 0) return "";
|
|
1621
1511
|
if (members.length === 1) return members[0];
|
|
1622
|
-
if (isDiscriminated) return `z.discriminatedUnion(${
|
|
1512
|
+
if (isDiscriminated) return `z.discriminatedUnion(${text.quote(node.discriminatorPropertyName)}, ${buildList(members)})`;
|
|
1623
1513
|
if (isXor) return `z.xor(${buildList(members)})`;
|
|
1624
1514
|
return `z.union(${buildList(members)})`;
|
|
1625
1515
|
},
|
|
1626
1516
|
intersection(node) {
|
|
1627
1517
|
const members = node.members ?? [];
|
|
1628
1518
|
if (members.length === 0) return "";
|
|
1629
|
-
const hasSiblingProperties = members.some((member) => member.type === ast.
|
|
1519
|
+
const hasSiblingProperties = members.some((member) => member.type === ast.schema.types.object && member.properties.length > 0);
|
|
1630
1520
|
const transform = (member) => {
|
|
1631
1521
|
if (!hasSiblingProperties) return this.transform(member);
|
|
1632
1522
|
unionsWithSiblings.add(member);
|
|
@@ -1640,7 +1530,18 @@ function buildZodNodes(dialect, cyclicSchemas) {
|
|
|
1640
1530
|
if (!first) return "";
|
|
1641
1531
|
const firstBase = transform(first);
|
|
1642
1532
|
if (!firstBase) return "";
|
|
1643
|
-
if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemas))
|
|
1533
|
+
if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemas)) {
|
|
1534
|
+
const discriminator = ast.schema.narrow(first, ast.schema.types.union)?.discriminatorPropertyName;
|
|
1535
|
+
return preserveRequiredProperties(members).slice(1).reduce((acc, member) => {
|
|
1536
|
+
const object = ast.schema.narrow(member, ast.schema.types.object);
|
|
1537
|
+
if (!discriminator || !object?.properties.some((property) => property.name === discriminator)) return dialect.extend(acc, dialect.objectShape(this, member));
|
|
1538
|
+
const properties = object.properties.filter((property) => property.name !== discriminator);
|
|
1539
|
+
return properties.length ? dialect.extend(acc, dialect.objectShape(this, {
|
|
1540
|
+
...object,
|
|
1541
|
+
properties
|
|
1542
|
+
})) : acc;
|
|
1543
|
+
}, firstBase);
|
|
1544
|
+
}
|
|
1644
1545
|
return rest.reduce((acc, member) => {
|
|
1645
1546
|
const constraint = getMemberConstraint({
|
|
1646
1547
|
member,
|
|
@@ -1655,15 +1556,57 @@ function buildZodNodes(dialect, cyclicSchemas) {
|
|
|
1655
1556
|
}
|
|
1656
1557
|
};
|
|
1657
1558
|
}
|
|
1559
|
+
function containsCheck(ctx, { schema, min = 1, max }) {
|
|
1560
|
+
if (min === 0 && max === void 0) return null;
|
|
1561
|
+
const countBounds = [`count >= ${min}`, max !== void 0 && `count <= ${max}`].filter(Boolean).join(" && ");
|
|
1562
|
+
const message = max !== void 0 ? `Array must contain ${min} to ${max} matching items` : `Array must contain at least ${min} matching items`;
|
|
1563
|
+
return `(items) => { const count = items.filter((item) => ${ctx.transform(schema) ?? "z.unknown()"}.safeParse(item).success).length; return ${countBounds} }, { message: ${text.quote(message)} }`;
|
|
1564
|
+
}
|
|
1565
|
+
function conditionExpression(ctx, schema) {
|
|
1566
|
+
if (!schema) return "true";
|
|
1567
|
+
const object = ast.schema.narrow(schema, ast.schema.types.object);
|
|
1568
|
+
const keyOnlyNames = object?.properties.filter((property) => property.required && property.schema.type === ast.schema.types.unknown).map((property) => property.name) ?? [];
|
|
1569
|
+
const restSchema = object ? {
|
|
1570
|
+
...object,
|
|
1571
|
+
properties: object.properties.filter((property) => !keyOnlyNames.includes(property.name))
|
|
1572
|
+
} : schema;
|
|
1573
|
+
const isOnlyKeys = keyOnlyNames.length > 0 && restSchema.type === "object" && restSchema.properties.length === 0 && restSchema.additionalProperties === void 0 && !restSchema.patternProperties && !restSchema.propertyNames && !hasInputChecks(restSchema);
|
|
1574
|
+
return [keyOnlyNames.length > 0 && `typeof value === "object" && value !== null && ${JSON.stringify(keyOnlyNames)}.every((key) => key in value)`, !isOnlyKeys && `${ctx.transform(restSchema) ?? "z.unknown()"}.safeParse(value).success`].filter(Boolean).join(" && ");
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Wraps each handler so its output first checks the raw input, see {@link inputChecks}.
|
|
1578
|
+
*/
|
|
1579
|
+
function withInputChecks(nodes) {
|
|
1580
|
+
const wrapHandler = (type) => {
|
|
1581
|
+
const handler = nodes[type];
|
|
1582
|
+
if (!handler) return [];
|
|
1583
|
+
const checkedHandler = function(node) {
|
|
1584
|
+
const output = handler.call(this, node);
|
|
1585
|
+
if (!output || !hasInputChecks(node)) return output;
|
|
1586
|
+
return inputChecks({
|
|
1587
|
+
value: output,
|
|
1588
|
+
node,
|
|
1589
|
+
keySchema: hasKeyCheck(node) ? this.transform(node.propertyNames) : void 0,
|
|
1590
|
+
conditionals: (node.conditionals ?? []).map((entry) => `z.refine((value) => (${conditionExpression(this, entry.if)} ? ${conditionExpression(this, entry.then)} : ${conditionExpression(this, entry.else)}), { message: "Value does not meet a conditional schema" })`)
|
|
1591
|
+
});
|
|
1592
|
+
};
|
|
1593
|
+
return [[type, checkedHandler]];
|
|
1594
|
+
};
|
|
1595
|
+
return Object.fromEntries(Object.values(ast.schema.types).flatMap(wrapHandler));
|
|
1596
|
+
}
|
|
1658
1597
|
// src/printers/printerZod.ts
|
|
1659
1598
|
function strictOneOfMember(member, node, cyclicSchemas) {
|
|
1660
|
-
if (node.type === ast.
|
|
1599
|
+
if (node.type === ast.schema.types.ref) {
|
|
1661
1600
|
if (member.startsWith("z.lazy(")) return member;
|
|
1662
|
-
const refName = ast.
|
|
1601
|
+
const refName = ast.ref.getName(node);
|
|
1663
1602
|
if (refName && cyclicSchemas?.has(refName)) return member;
|
|
1664
|
-
const schema =
|
|
1603
|
+
const schema = ast.ref.mergeSchema(node);
|
|
1665
1604
|
if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
|
|
1666
|
-
if (schema.type === ast.
|
|
1605
|
+
if (schema.type === ast.schema.types.object && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) {
|
|
1606
|
+
const checks = [node, node.schema].filter((entry) => entry && hasInputChecks(entry)).length;
|
|
1607
|
+
if (checks === 2) return member;
|
|
1608
|
+
return checks ? `z.pipe(${member}.in, ${member}.out.strict())` : `${member}.strict()`;
|
|
1609
|
+
}
|
|
1667
1610
|
}
|
|
1668
1611
|
return member;
|
|
1669
1612
|
}
|
|
@@ -1679,7 +1622,7 @@ function buildZodObjectShape(ctx, node) {
|
|
|
1679
1622
|
optional: schema.optional || required === false,
|
|
1680
1623
|
nullish: schema.nullish,
|
|
1681
1624
|
defaultValue: meta.default,
|
|
1682
|
-
description: schema.type !== ast.
|
|
1625
|
+
description: schema.type !== ast.schema.types.ref && meta.type === ast.schema.types.ref ? void 0 : meta.description,
|
|
1683
1626
|
examples: meta.examples
|
|
1684
1627
|
});
|
|
1685
1628
|
if (!cyclic) return `${objectKey(name)}: ${body}`;
|
|
@@ -1700,21 +1643,22 @@ function buildZodObjectShape(ctx, node) {
|
|
|
1700
1643
|
function recursiveGetterType({ ctx, schema, required }) {
|
|
1701
1644
|
function baseType(schema) {
|
|
1702
1645
|
if (ctx.options.nodes?.[schema.type]) return void 0;
|
|
1703
|
-
if (schema.type === ast.
|
|
1646
|
+
if (schema.type === ast.schema.types.ref) {
|
|
1704
1647
|
const saved = ctx.options.cyclicSchemas;
|
|
1705
1648
|
ctx.options.cyclicSchemas = void 0;
|
|
1706
1649
|
const name = ctx.transform(schema);
|
|
1707
1650
|
ctx.options.cyclicSchemas = saved;
|
|
1708
1651
|
return name ? `typeof ${name}` : void 0;
|
|
1709
1652
|
}
|
|
1710
|
-
if (schema.type === ast.
|
|
1653
|
+
if (schema.type === ast.schema.types.array && schema.items?.length === 1) {
|
|
1711
1654
|
const item = baseType(schema.items[0]);
|
|
1712
|
-
|
|
1655
|
+
if (!item) return void 0;
|
|
1656
|
+
return `z.ZodArray<${schema.items[0].nullable ? `z.ZodNullable<${item}>` : item}>`;
|
|
1713
1657
|
}
|
|
1714
1658
|
}
|
|
1715
1659
|
const base = baseType(schema);
|
|
1716
1660
|
if (!base) return void 0;
|
|
1717
|
-
const meta =
|
|
1661
|
+
const meta = ast.ref.mergeSchema(schema);
|
|
1718
1662
|
const optional = schema.optional || required === false;
|
|
1719
1663
|
const wrapped = (() => {
|
|
1720
1664
|
if (schema.nullish || meta.nullable && optional) return `z.ZodOptional<z.ZodNullable<${base}>>`;
|
|
@@ -1729,12 +1673,12 @@ function recursiveGetterType({ ctx, schema, required }) {
|
|
|
1729
1673
|
* `direction` themselves, so {@link containsDirectionalNode} probes their children instead.
|
|
1730
1674
|
*/
|
|
1731
1675
|
const CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
1732
|
-
ast.
|
|
1733
|
-
ast.
|
|
1734
|
-
ast.
|
|
1735
|
-
ast.
|
|
1736
|
-
ast.
|
|
1737
|
-
ast.
|
|
1676
|
+
ast.schema.types.object,
|
|
1677
|
+
ast.schema.types.array,
|
|
1678
|
+
ast.schema.types.tuple,
|
|
1679
|
+
ast.schema.types.union,
|
|
1680
|
+
ast.schema.types.intersection,
|
|
1681
|
+
ast.schema.types.ref
|
|
1738
1682
|
]);
|
|
1739
1683
|
/**
|
|
1740
1684
|
* Runs the node's effective handler (a `printer.nodes` override, else the built-in) once per
|
|
@@ -1765,7 +1709,7 @@ function variesByDirection({ node, printerOptions }) {
|
|
|
1765
1709
|
*/
|
|
1766
1710
|
function containsDirectionalNode({ node, printerOptions }) {
|
|
1767
1711
|
if (!node) return false;
|
|
1768
|
-
return ast.
|
|
1712
|
+
return ast.schema.find(node, {
|
|
1769
1713
|
predicate: (schema) => variesByDirection({
|
|
1770
1714
|
node: schema,
|
|
1771
1715
|
printerOptions
|
|
@@ -1777,10 +1721,10 @@ function containsDirectionalNode({ node, printerOptions }) {
|
|
|
1777
1721
|
* Names of the `$ref` schemas the generator should route to their input (encode) variant.
|
|
1778
1722
|
*/
|
|
1779
1723
|
function collectDirectionalRefNames({ node, printerOptions }) {
|
|
1780
|
-
return ast.
|
|
1724
|
+
return ast.node.collect(node, { schema: (n) => n.type === ast.schema.types.ref && n.ref && containsDirectionalNode({
|
|
1781
1725
|
node: n,
|
|
1782
1726
|
printerOptions
|
|
1783
|
-
}) ? ast.
|
|
1727
|
+
}) ? ast.ref.getName(n) ?? void 0 : void 0 });
|
|
1784
1728
|
}
|
|
1785
1729
|
/**
|
|
1786
1730
|
* Handlers that never recurse into children, so {@link variesByDirection} can call one directly
|
|
@@ -1802,6 +1746,7 @@ const standardDialect = {
|
|
|
1802
1746
|
coercedNumber: (schema) => `z.coerce.number().pipe(${schema})`,
|
|
1803
1747
|
catchall: (object, value) => `${object}.catchall(${value})`,
|
|
1804
1748
|
nullable: (value) => `${value}.nullable()`,
|
|
1749
|
+
refine: (value, check) => `${value}.refine(${check})`,
|
|
1805
1750
|
unique: (array) => `${array}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`,
|
|
1806
1751
|
extend: (base, shape) => `${base}.extend(${shape})`,
|
|
1807
1752
|
intersection: (left, right) => `${left}.and(${right})`,
|
|
@@ -1813,8 +1758,8 @@ const standardDialect = {
|
|
|
1813
1758
|
};
|
|
1814
1759
|
const scalarNodes = {
|
|
1815
1760
|
...buildScalarNodes(standardDialect),
|
|
1816
|
-
bigint() {
|
|
1817
|
-
return
|
|
1761
|
+
bigint(node) {
|
|
1762
|
+
return `z.coerce.bigint()${numberConstraints(bigintConstraints(node), "n")}`;
|
|
1818
1763
|
},
|
|
1819
1764
|
date(node) {
|
|
1820
1765
|
if (node.representation !== "date") return "z.iso.date()";
|
|
@@ -1847,14 +1792,14 @@ const scalarNodes = {
|
|
|
1847
1792
|
* const code = printer.print(stringNode) // "z.string()"
|
|
1848
1793
|
* ```
|
|
1849
1794
|
*/
|
|
1850
|
-
const printerZod =
|
|
1795
|
+
const printerZod = createPrinter((options) => {
|
|
1851
1796
|
return {
|
|
1852
1797
|
name: "zod",
|
|
1853
1798
|
options,
|
|
1854
|
-
nodes: {
|
|
1799
|
+
nodes: withInputChecks({
|
|
1855
1800
|
...buildZodNodes(standardDialect, options.cyclicSchemas),
|
|
1856
1801
|
...scalarNodes
|
|
1857
|
-
},
|
|
1802
|
+
}),
|
|
1858
1803
|
overrides: options.nodes,
|
|
1859
1804
|
print(node) {
|
|
1860
1805
|
return printWithOmit(this, node, (value, meta) => applyModifiers({
|
|
@@ -1911,18 +1856,19 @@ const miniDialect = {
|
|
|
1911
1856
|
coercedNumber: (schema) => `z.pipe(z.coerce.number(), ${schema})`,
|
|
1912
1857
|
catchall: (object, value) => `z.catchall(${object}, ${value})`,
|
|
1913
1858
|
nullable: (value) => `z.nullable(${value})`,
|
|
1859
|
+
refine: (value, check) => `${value}.check(z.refine(${check}))`,
|
|
1914
1860
|
unique: (array) => `${array}.check(z.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" }))`,
|
|
1915
1861
|
extend: (base, shape) => `z.extend(${base}, ${shape})`,
|
|
1916
1862
|
intersection: (left, right) => `z.intersection(${left}, ${right})`
|
|
1917
1863
|
};
|
|
1918
|
-
const printerZodMini =
|
|
1864
|
+
const printerZodMini = createPrinter((options) => {
|
|
1919
1865
|
return {
|
|
1920
1866
|
name: "zod-mini",
|
|
1921
1867
|
options,
|
|
1922
|
-
nodes: {
|
|
1868
|
+
nodes: withInputChecks({
|
|
1923
1869
|
...buildZodNodes(miniDialect, options.cyclicSchemas),
|
|
1924
1870
|
bigint(node) {
|
|
1925
|
-
return `z.coerce.bigint()${numberChecksMini(node)}`;
|
|
1871
|
+
return `z.coerce.bigint()${numberChecksMini(bigintConstraints(node), "n")}`;
|
|
1926
1872
|
},
|
|
1927
1873
|
date(node) {
|
|
1928
1874
|
if (node.representation === "string") return "z.iso.date()";
|
|
@@ -1935,7 +1881,7 @@ const printerZodMini = ast.createPrinter((options) => {
|
|
|
1935
1881
|
if (node.representation === "string") return "z.iso.time()";
|
|
1936
1882
|
return "z.date()";
|
|
1937
1883
|
}
|
|
1938
|
-
},
|
|
1884
|
+
}),
|
|
1939
1885
|
overrides: options.nodes,
|
|
1940
1886
|
print(node) {
|
|
1941
1887
|
return printWithOmit(this, node, (value, meta) => applyMiniModifiers({
|
|
@@ -2022,7 +1968,8 @@ const zodGenerator = defineGenerator({
|
|
|
2022
1968
|
node,
|
|
2023
1969
|
root,
|
|
2024
1970
|
output,
|
|
2025
|
-
group: group ?? void 0
|
|
1971
|
+
group: group ?? void 0,
|
|
1972
|
+
checks: true
|
|
2026
1973
|
});
|
|
2027
1974
|
const inputImportEntries = hasDirectionalNode ? [...directionalRefNames].map((schemaName) => ({
|
|
2028
1975
|
name: [resolver.schema.inputName(schemaName)],
|
|
@@ -2113,7 +2060,7 @@ const zodGenerator = defineGenerator({
|
|
|
2113
2060
|
});
|
|
2114
2061
|
},
|
|
2115
2062
|
operation(node, ctx) {
|
|
2116
|
-
if (!ast.
|
|
2063
|
+
if (!ast.http.isOperation(node)) return null;
|
|
2117
2064
|
const { adapter, config, resolver, root } = ctx;
|
|
2118
2065
|
const { output, coercion, guidType, regexType, oneOfType, compile, mini, inferred, importPath, group, printer } = ctx.options;
|
|
2119
2066
|
const dateType = getOasAdapter(adapter).options.dateType;
|
|
@@ -2142,17 +2089,18 @@ const zodGenerator = defineGenerator({
|
|
|
2142
2089
|
node: schema,
|
|
2143
2090
|
printerOptions
|
|
2144
2091
|
})) : null;
|
|
2092
|
+
const schemaPrinter = getPrinter(resolver, {
|
|
2093
|
+
...printerOptions,
|
|
2094
|
+
keysToOmit
|
|
2095
|
+
}, mini, direction);
|
|
2145
2096
|
const imports = resolver.imports({
|
|
2146
2097
|
node: schema,
|
|
2147
2098
|
root,
|
|
2148
2099
|
output,
|
|
2149
2100
|
group: group ?? void 0,
|
|
2101
|
+
checks: true,
|
|
2150
2102
|
name: (schemaName) => directionalRefNames?.has(schemaName) ? resolver.schema.inputName(schemaName) : resolver.name(schemaName)
|
|
2151
2103
|
});
|
|
2152
|
-
const schemaPrinter = getPrinter(resolver, {
|
|
2153
|
-
...printerOptions,
|
|
2154
|
-
keysToOmit
|
|
2155
|
-
}, mini, direction);
|
|
2156
2104
|
return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
2157
2105
|
root: meta.file.path,
|
|
2158
2106
|
path: imp.path,
|
|
@@ -2192,7 +2140,7 @@ const zodGenerator = defineGenerator({
|
|
|
2192
2140
|
})] });
|
|
2193
2141
|
}
|
|
2194
2142
|
function buildResponseUnion({ responses, name, fallbackUnknown }) {
|
|
2195
|
-
if (new Set(
|
|
2143
|
+
if (new Set(ast.http.responses.getImportedNames(responses).map((refName) => resolver.name(refName))).has(name)) return null;
|
|
2196
2144
|
const members = responses.map((res) => ast.factory.createSchema({
|
|
2197
2145
|
type: "ref",
|
|
2198
2146
|
name: resolver.response.status(node, res.statusCode)
|
|
@@ -2225,14 +2173,27 @@ const zodGenerator = defineGenerator({
|
|
|
2225
2173
|
keysToOmit: primary?.keysToOmit
|
|
2226
2174
|
});
|
|
2227
2175
|
});
|
|
2176
|
+
const toParamsSchema = (params) => ast.factory.createSchema({
|
|
2177
|
+
type: "object",
|
|
2178
|
+
primitive: "object",
|
|
2179
|
+
properties: params.map((param) => ast.factory.createProperty({
|
|
2180
|
+
name: param.name,
|
|
2181
|
+
required: param.required,
|
|
2182
|
+
schema: param.schema
|
|
2183
|
+
}))
|
|
2184
|
+
});
|
|
2185
|
+
const responseHeaderSchemas = node.responses.map((res) => res.headers?.length ? renderSchemaEntry({
|
|
2186
|
+
schema: toParamsSchema(res.headers),
|
|
2187
|
+
name: resolver.response.headers(node, res.statusCode)
|
|
2188
|
+
}) : null);
|
|
2228
2189
|
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
2229
|
-
const successResponsesWithSchema =
|
|
2190
|
+
const successResponsesWithSchema = responsesWithSchema.filter((res) => ast.http.status.isSuccess(res));
|
|
2230
2191
|
const responseUnionSchema = responsesWithSchema.length > 0 ? buildResponseUnion({
|
|
2231
2192
|
responses: successResponsesWithSchema,
|
|
2232
2193
|
name: resolver.response.response(node),
|
|
2233
2194
|
fallbackUnknown: true
|
|
2234
2195
|
}) : null;
|
|
2235
|
-
const errorResponsesWithSchema = responsesWithSchema.filter((res) => !
|
|
2196
|
+
const errorResponsesWithSchema = responsesWithSchema.filter((res) => !ast.http.status.isSuccess(res));
|
|
2236
2197
|
const errorUnionSchema = errorResponsesWithSchema.length > 0 ? buildResponseUnion({
|
|
2237
2198
|
responses: errorResponsesWithSchema,
|
|
2238
2199
|
name: resolver.response.error(node),
|
|
@@ -2259,7 +2220,7 @@ const zodGenerator = defineGenerator({
|
|
|
2259
2220
|
description: node.requestBody.description ?? schema.description
|
|
2260
2221
|
}), "encode");
|
|
2261
2222
|
})();
|
|
2262
|
-
const { path, query, header, cookie } =
|
|
2223
|
+
const { path, query, header, cookie } = ast.http.groupParameters(node);
|
|
2263
2224
|
const paramGroupSchemas = [
|
|
2264
2225
|
{
|
|
2265
2226
|
kind: "path",
|
|
@@ -2278,15 +2239,7 @@ const zodGenerator = defineGenerator({
|
|
|
2278
2239
|
params: cookie
|
|
2279
2240
|
}
|
|
2280
2241
|
].filter(({ params }) => params.length > 0).map(({ kind, params }) => renderSchemaEntry({
|
|
2281
|
-
schema:
|
|
2282
|
-
type: "object",
|
|
2283
|
-
primitive: "object",
|
|
2284
|
-
properties: params.map((param) => ast.factory.createProperty({
|
|
2285
|
-
name: param.name,
|
|
2286
|
-
required: param.required,
|
|
2287
|
-
schema: param.schema
|
|
2288
|
-
}))
|
|
2289
|
-
}),
|
|
2242
|
+
schema: toParamsSchema(params),
|
|
2290
2243
|
name: resolver.param[kind](node, params[0]),
|
|
2291
2244
|
direction: "encode"
|
|
2292
2245
|
}));
|
|
@@ -2327,6 +2280,7 @@ const zodGenerator = defineGenerator({
|
|
|
2327
2280
|
}),
|
|
2328
2281
|
paramSchemas,
|
|
2329
2282
|
responseSchemas,
|
|
2283
|
+
responseHeaderSchemas,
|
|
2330
2284
|
responseUnionSchema,
|
|
2331
2285
|
errorUnionSchema,
|
|
2332
2286
|
requestSchema,
|
|
@@ -2339,11 +2293,11 @@ const zodGenerator = defineGenerator({
|
|
|
2339
2293
|
});
|
|
2340
2294
|
// kubb-extension.json
|
|
2341
2295
|
var compatibility = {
|
|
2342
|
-
"kubb": ">=5.
|
|
2296
|
+
"kubb": ">=5.18.4",
|
|
2343
2297
|
"node": ">=22.22"
|
|
2344
2298
|
};
|
|
2345
2299
|
// package.json
|
|
2346
|
-
var version = "5.9.0
|
|
2300
|
+
var version = "5.9.0";
|
|
2347
2301
|
// src/resolvers/resolverZod.ts
|
|
2348
2302
|
/**
|
|
2349
2303
|
* Default resolver used by `@kubb/plugin-zod`. Decides the names and file
|
|
@@ -2363,15 +2317,15 @@ var version = "5.9.0-canary.20261111T122006";
|
|
|
2363
2317
|
const resolverZod = createResolver({
|
|
2364
2318
|
pluginName: "plugin-zod",
|
|
2365
2319
|
name(name) {
|
|
2366
|
-
return this.memo(`name:${name}`, () => ensureValidVarName(
|
|
2320
|
+
return this.memo(`name:${name}`, () => ensureValidVarName(casing.camel(name, { suffix: "schema" })));
|
|
2367
2321
|
},
|
|
2368
|
-
file: createCasedFile((part) =>
|
|
2322
|
+
file: createCasedFile((part) => casing.camel(part, { suffix: "schema" })),
|
|
2369
2323
|
schema: {
|
|
2370
2324
|
typeName(name) {
|
|
2371
|
-
return this.memo(`typeName:${name}`, () => ensureValidVarName(
|
|
2325
|
+
return this.memo(`typeName:${name}`, () => ensureValidVarName(casing.pascal(name, { suffix: "schema type" })));
|
|
2372
2326
|
},
|
|
2373
2327
|
type(name) {
|
|
2374
|
-
return this.memo(`type:${name}`, () => ensureValidVarName(
|
|
2328
|
+
return this.memo(`type:${name}`, () => ensureValidVarName(casing.pascal(name, { suffix: "type" })));
|
|
2375
2329
|
},
|
|
2376
2330
|
inputName(name) {
|
|
2377
2331
|
return this.name(`${name} input`);
|
|
@@ -2380,10 +2334,10 @@ const resolverZod = createResolver({
|
|
|
2380
2334
|
return this.schema.typeName(`${name} input`);
|
|
2381
2335
|
},
|
|
2382
2336
|
isName(name) {
|
|
2383
|
-
return ensureValidVarName(
|
|
2337
|
+
return ensureValidVarName(casing.camel(name, { prefix: "is" }));
|
|
2384
2338
|
},
|
|
2385
2339
|
assertName(name) {
|
|
2386
|
-
return ensureValidVarName(
|
|
2340
|
+
return ensureValidVarName(casing.camel(name, { prefix: "assert" }));
|
|
2387
2341
|
}
|
|
2388
2342
|
},
|
|
2389
2343
|
param: createOperationParamResolver(),
|
|
@@ -2457,7 +2411,7 @@ const pluginZod = defineExtension({
|
|
|
2457
2411
|
exclude,
|
|
2458
2412
|
include,
|
|
2459
2413
|
override,
|
|
2460
|
-
group:
|
|
2414
|
+
group: group ?? null,
|
|
2461
2415
|
importPath,
|
|
2462
2416
|
coercion,
|
|
2463
2417
|
inferred: inferred === "direction" ? "codec" : inferred,
|