@kubb/plugin-zod 5.0.0-beta.30 → 5.0.0-beta.31
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 +312 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.js +312 -78
- package/dist/index.js.map +1 -1
- package/extension.yaml +2 -0
- package/package.json +5 -4
- package/src/components/Operations.tsx +2 -1
- package/src/generators/zodGenerator.tsx +166 -61
- package/src/plugin.ts +3 -13
- package/src/printers/printerZod.ts +24 -5
- package/src/resolvers/resolverZod.ts +6 -0
- package/src/types.ts +15 -0
- package/src/utils.ts +93 -0
package/dist/index.js
CHANGED
|
@@ -249,6 +249,89 @@ function ensureValidVarName(name) {
|
|
|
249
249
|
return `_${name}`;
|
|
250
250
|
}
|
|
251
251
|
//#endregion
|
|
252
|
+
//#region ../../internals/shared/src/operation.ts
|
|
253
|
+
/**
|
|
254
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
255
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
256
|
+
*/
|
|
257
|
+
function getContentTypeSuffix(contentType) {
|
|
258
|
+
const baseType = contentType.split(";")[0].trim();
|
|
259
|
+
if (baseType === "application/json") return "Json";
|
|
260
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
261
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
262
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
263
|
+
if (parts.length === 0) return "Unknown";
|
|
264
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
268
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
269
|
+
*/
|
|
270
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
271
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
272
|
+
return baseName + suffix;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
276
|
+
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
277
|
+
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
278
|
+
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
279
|
+
*/
|
|
280
|
+
function resolveContentTypeVariants(entries, baseName) {
|
|
281
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
282
|
+
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
283
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
284
|
+
let suffix = baseSuffix;
|
|
285
|
+
let name = getPerContentTypeName(baseName, suffix);
|
|
286
|
+
let counter = 2;
|
|
287
|
+
while (usedNames.has(name)) {
|
|
288
|
+
suffix = `${baseSuffix}${counter++}`;
|
|
289
|
+
name = getPerContentTypeName(baseName, suffix);
|
|
290
|
+
}
|
|
291
|
+
usedNames.add(name);
|
|
292
|
+
return {
|
|
293
|
+
name,
|
|
294
|
+
suffix,
|
|
295
|
+
schema: entry.schema,
|
|
296
|
+
keysToOmit: entry.keysToOmit,
|
|
297
|
+
contentType: entry.contentType
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region ../../internals/shared/src/group.ts
|
|
303
|
+
/**
|
|
304
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
305
|
+
* shared default naming so every plugin groups output consistently:
|
|
306
|
+
*
|
|
307
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
308
|
+
* - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
|
|
309
|
+
*
|
|
310
|
+
* Returns `null` when grouping is disabled, matching the per-plugin convention.
|
|
311
|
+
*
|
|
312
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
313
|
+
* @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
|
|
314
|
+
* @param options.honorName - When `true`, a user-provided `group.name` overrides the default namer.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-zod
|
|
319
|
+
* createGroupConfig(group, { suffix: 'Controller', honorName: true }) // plugin-faker, plugin-client, …
|
|
320
|
+
* createGroupConfig(group, { suffix: 'Requests', honorName: true }) // plugin-cypress, plugin-mcp
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
function createGroupConfig(group, options) {
|
|
324
|
+
if (!group) return null;
|
|
325
|
+
const defaultName = (ctx) => {
|
|
326
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
327
|
+
return `${camelCase(ctx.group)}${options.suffix}`;
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
...group,
|
|
331
|
+
name: options.honorName && group.name ? group.name : defaultName
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
252
335
|
//#region src/components/Operations.tsx
|
|
253
336
|
function Operations({ name, operations }) {
|
|
254
337
|
const operationsJSON = operations.reduce((prev, acc) => {
|
|
@@ -256,6 +339,7 @@ function Operations({ name, operations }) {
|
|
|
256
339
|
return prev;
|
|
257
340
|
}, {});
|
|
258
341
|
const pathsJSON = operations.reduce((prev, acc) => {
|
|
342
|
+
if (!ast.isHttpOperationNode(acc.node)) return prev;
|
|
259
343
|
prev[`"${acc.node.path}"`] = {
|
|
260
344
|
...prev[`"${acc.node.path}"`] ?? {},
|
|
261
345
|
[acc.node.method]: `operations["${acc.node.operationId}"]`
|
|
@@ -365,6 +449,61 @@ function shouldCoerce(coercion, type) {
|
|
|
365
449
|
return !!coercion[type];
|
|
366
450
|
}
|
|
367
451
|
/**
|
|
452
|
+
* Registered codecs, checked in order.
|
|
453
|
+
*/
|
|
454
|
+
const codecs = [{
|
|
455
|
+
matches(node) {
|
|
456
|
+
return node.type === "date" && node.representation === "date";
|
|
457
|
+
},
|
|
458
|
+
decode(node) {
|
|
459
|
+
return node.format === "date" ? "z.iso.date().transform((value) => new Date(value))" : "z.iso.datetime().transform((value) => new Date(value))";
|
|
460
|
+
},
|
|
461
|
+
encode(node) {
|
|
462
|
+
return node.format === "date" ? "z.date().transform((value) => value.toISOString().slice(0, 10))" : "z.date().transform((value) => value.toISOString())";
|
|
463
|
+
}
|
|
464
|
+
}];
|
|
465
|
+
/**
|
|
466
|
+
* Returns the codec for this node, or `undefined` when the node needs no
|
|
467
|
+
* encode/decode (its wire and runtime types match).
|
|
468
|
+
*/
|
|
469
|
+
function getCodec(node) {
|
|
470
|
+
if (!node) return void 0;
|
|
471
|
+
return codecs.find((codec) => codec.matches(node));
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Returns `true` when the node itself is encoded/decoded by a codec.
|
|
475
|
+
*/
|
|
476
|
+
function hasCodec(node) {
|
|
477
|
+
return getCodec(node) !== void 0;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Returns `true` when the schema transitively contains a codec node —
|
|
481
|
+
* a value whose runtime type differs from its wire type (see {@link hasCodec}),
|
|
482
|
+
* so it must be decoded (response) or encoded (request) at the validation boundary.
|
|
483
|
+
* `$ref`s are followed via their resolved schema; a `seen` set guards cycles.
|
|
484
|
+
*/
|
|
485
|
+
function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
|
|
486
|
+
if (!node) return false;
|
|
487
|
+
if (hasCodec(node)) return true;
|
|
488
|
+
if (node.type === "ref") {
|
|
489
|
+
if (!node.ref) return false;
|
|
490
|
+
const refName = ast.extractRefName(node.ref);
|
|
491
|
+
if (refName) {
|
|
492
|
+
if (seen.has(refName)) return false;
|
|
493
|
+
seen.add(refName);
|
|
494
|
+
}
|
|
495
|
+
const resolved = ast.syncSchemaRef(node);
|
|
496
|
+
if (resolved.type === "ref") return false;
|
|
497
|
+
return containsCodec(resolved, seen);
|
|
498
|
+
}
|
|
499
|
+
const children = [];
|
|
500
|
+
if ("properties" in node && node.properties) children.push(...node.properties.map((prop) => prop.schema));
|
|
501
|
+
if ("items" in node && node.items) children.push(...node.items);
|
|
502
|
+
if ("members" in node && node.members) children.push(...node.members);
|
|
503
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
504
|
+
return children.some((child) => containsCodec(child, seen));
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
368
507
|
* Collects all resolved schema names for an operation's parameters and responses
|
|
369
508
|
* into a single lookup object, useful for building imports and type references.
|
|
370
509
|
*/
|
|
@@ -536,8 +675,9 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
536
675
|
return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
|
|
537
676
|
},
|
|
538
677
|
date(node) {
|
|
539
|
-
|
|
540
|
-
return
|
|
678
|
+
const codec = getCodec(node);
|
|
679
|
+
if (codec) return this.options.direction === "input" ? codec.encode(node) : codec.decode(node);
|
|
680
|
+
return "z.iso.date()";
|
|
541
681
|
},
|
|
542
682
|
datetime(node) {
|
|
543
683
|
const offset = node.offset || this.options.dateType === "stringOffset";
|
|
@@ -574,7 +714,8 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
574
714
|
ref(node) {
|
|
575
715
|
if (!node.name) return null;
|
|
576
716
|
const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
|
|
577
|
-
const
|
|
717
|
+
const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
|
|
718
|
+
const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.resolveInputSchemaName(refName) ?? refName : this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
578
719
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
579
720
|
return resolvedName;
|
|
580
721
|
},
|
|
@@ -841,6 +982,54 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
841
982
|
const zodPrinterCache = /* @__PURE__ */ new WeakMap();
|
|
842
983
|
const zodMiniPrinterCache = /* @__PURE__ */ new WeakMap();
|
|
843
984
|
/**
|
|
985
|
+
* Returns the cached `output`/`input` direction printers for a resolver, building them on
|
|
986
|
+
* first use. The `input` printer encodes `Date → string` for request bodies; `output` decodes
|
|
987
|
+
* `string → Date` for responses. Schemas without `dateType: 'date'` fields print identically.
|
|
988
|
+
*/
|
|
989
|
+
function getStdPrinters(resolver, params) {
|
|
990
|
+
const cached = zodPrinterCache.get(resolver);
|
|
991
|
+
if (cached && cached.coercion === params.coercion && cached.guidType === params.guidType && cached.dateType === params.dateType) return {
|
|
992
|
+
output: cached.output,
|
|
993
|
+
input: cached.input
|
|
994
|
+
};
|
|
995
|
+
const base = {
|
|
996
|
+
...params,
|
|
997
|
+
resolver
|
|
998
|
+
};
|
|
999
|
+
const output = printerZod({
|
|
1000
|
+
...base,
|
|
1001
|
+
direction: "output"
|
|
1002
|
+
});
|
|
1003
|
+
const input = printerZod({
|
|
1004
|
+
...base,
|
|
1005
|
+
direction: "input"
|
|
1006
|
+
});
|
|
1007
|
+
zodPrinterCache.set(resolver, {
|
|
1008
|
+
output,
|
|
1009
|
+
input,
|
|
1010
|
+
coercion: params.coercion,
|
|
1011
|
+
guidType: params.guidType,
|
|
1012
|
+
dateType: params.dateType
|
|
1013
|
+
});
|
|
1014
|
+
return {
|
|
1015
|
+
output,
|
|
1016
|
+
input
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
function getMiniPrinter(resolver, params) {
|
|
1020
|
+
const cached = zodMiniPrinterCache.get(resolver);
|
|
1021
|
+
if (cached && cached.guidType === params.guidType) return cached.printer;
|
|
1022
|
+
const p = printerZodMini({
|
|
1023
|
+
...params,
|
|
1024
|
+
resolver
|
|
1025
|
+
});
|
|
1026
|
+
zodMiniPrinterCache.set(resolver, {
|
|
1027
|
+
printer: p,
|
|
1028
|
+
guidType: params.guidType
|
|
1029
|
+
});
|
|
1030
|
+
return p;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
844
1033
|
* Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
|
|
845
1034
|
* schema in the spec plus per-operation request/response/parameter schemas.
|
|
846
1035
|
* When `mini: true`, schemas use the Zod Mini functional API instead of
|
|
@@ -856,7 +1045,10 @@ const zodGenerator = defineGenerator({
|
|
|
856
1045
|
if (!node.name) return;
|
|
857
1046
|
const mode = ctx.getMode(output);
|
|
858
1047
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
859
|
-
const
|
|
1048
|
+
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1049
|
+
const hasCodec = !mini && containsCodec(node);
|
|
1050
|
+
const codecRefNames = new Set(hasCodec ? ast.collect(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.extractRefName(n.ref) ?? void 0 : void 0 }) : []);
|
|
1051
|
+
const importEntries = adapter.getImports(node, (schemaName) => ({
|
|
860
1052
|
name: resolver.resolveSchemaName(schemaName),
|
|
861
1053
|
path: resolver.resolveFile({
|
|
862
1054
|
name: schemaName,
|
|
@@ -867,6 +1059,24 @@ const zodGenerator = defineGenerator({
|
|
|
867
1059
|
group: group ?? void 0
|
|
868
1060
|
}).path
|
|
869
1061
|
}));
|
|
1062
|
+
const inputImportEntries = hasCodec ? [...codecRefNames].map((schemaName) => ({
|
|
1063
|
+
name: resolver.resolveInputSchemaName(schemaName),
|
|
1064
|
+
path: resolver.resolveFile({
|
|
1065
|
+
name: schemaName,
|
|
1066
|
+
extname: ".ts"
|
|
1067
|
+
}, {
|
|
1068
|
+
root,
|
|
1069
|
+
output,
|
|
1070
|
+
group: group ?? void 0
|
|
1071
|
+
}).path
|
|
1072
|
+
})) : [];
|
|
1073
|
+
const seenImports = /* @__PURE__ */ new Set();
|
|
1074
|
+
const imports = [...importEntries, ...inputImportEntries].filter((imp) => {
|
|
1075
|
+
const key = `${Array.isArray(imp.name) ? imp.name.join(",") : imp.name}|${imp.path}`;
|
|
1076
|
+
if (seenImports.has(key)) return false;
|
|
1077
|
+
seenImports.add(key);
|
|
1078
|
+
return true;
|
|
1079
|
+
});
|
|
870
1080
|
const meta = {
|
|
871
1081
|
name: resolver.resolveSchemaName(node.name),
|
|
872
1082
|
file: resolver.resolveFile({
|
|
@@ -879,44 +1089,20 @@ const zodGenerator = defineGenerator({
|
|
|
879
1089
|
})
|
|
880
1090
|
};
|
|
881
1091
|
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null;
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
zodPrinterCache.set(resolver, {
|
|
897
|
-
printer: p,
|
|
898
|
-
coercion,
|
|
899
|
-
guidType,
|
|
900
|
-
dateType
|
|
901
|
-
});
|
|
902
|
-
return p;
|
|
903
|
-
}
|
|
904
|
-
function getCachedMiniPrinter() {
|
|
905
|
-
const cached = zodMiniPrinterCache.get(resolver);
|
|
906
|
-
if (cached && cached.guidType === guidType) return cached.printer;
|
|
907
|
-
const p = printerZodMini({
|
|
908
|
-
guidType,
|
|
909
|
-
wrapOutput,
|
|
910
|
-
resolver,
|
|
911
|
-
cyclicSchemas,
|
|
912
|
-
nodes: printer?.nodes
|
|
913
|
-
});
|
|
914
|
-
zodMiniPrinterCache.set(resolver, {
|
|
915
|
-
printer: p,
|
|
916
|
-
guidType
|
|
917
|
-
});
|
|
918
|
-
return p;
|
|
919
|
-
}
|
|
1092
|
+
const stdPrinters = mini ? null : getStdPrinters(resolver, {
|
|
1093
|
+
coercion,
|
|
1094
|
+
guidType,
|
|
1095
|
+
dateType,
|
|
1096
|
+
wrapOutput,
|
|
1097
|
+
cyclicSchemas,
|
|
1098
|
+
nodes: printer?.nodes
|
|
1099
|
+
});
|
|
1100
|
+
const schemaPrinter = mini ? getMiniPrinter(resolver, {
|
|
1101
|
+
guidType,
|
|
1102
|
+
wrapOutput,
|
|
1103
|
+
cyclicSchemas,
|
|
1104
|
+
nodes: printer?.nodes
|
|
1105
|
+
}) : stdPrinters.output;
|
|
920
1106
|
return /* @__PURE__ */ jsxs(File, {
|
|
921
1107
|
baseName: meta.file.baseName,
|
|
922
1108
|
path: meta.file.path,
|
|
@@ -947,17 +1133,28 @@ const zodGenerator = defineGenerator({
|
|
|
947
1133
|
root: meta.file.path,
|
|
948
1134
|
path: imp.path,
|
|
949
1135
|
name: imp.name
|
|
950
|
-
}, [
|
|
1136
|
+
}, [
|
|
1137
|
+
node.name,
|
|
1138
|
+
imp.path,
|
|
1139
|
+
imp.name
|
|
1140
|
+
].join("-"))),
|
|
951
1141
|
/* @__PURE__ */ jsx(Zod, {
|
|
952
1142
|
name: meta.name,
|
|
953
1143
|
node,
|
|
954
1144
|
printer: schemaPrinter,
|
|
955
1145
|
inferTypeName
|
|
1146
|
+
}),
|
|
1147
|
+
hasCodec && stdPrinters && /* @__PURE__ */ jsx(Zod, {
|
|
1148
|
+
name: resolver.resolveInputSchemaName(node.name),
|
|
1149
|
+
node,
|
|
1150
|
+
printer: stdPrinters.input,
|
|
1151
|
+
inferTypeName: inferred ? resolver.resolveInputSchemaTypeName(node.name) : null
|
|
956
1152
|
})
|
|
957
1153
|
]
|
|
958
1154
|
});
|
|
959
1155
|
},
|
|
960
1156
|
operation(node, ctx) {
|
|
1157
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
961
1158
|
const { adapter, config, resolver, root } = ctx;
|
|
962
1159
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
|
|
963
1160
|
const dateType = adapter.options.dateType;
|
|
@@ -975,11 +1172,12 @@ const zodGenerator = defineGenerator({
|
|
|
975
1172
|
group: group ?? void 0
|
|
976
1173
|
}) };
|
|
977
1174
|
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
978
|
-
function renderSchemaEntry({ schema, name, keysToOmit }) {
|
|
1175
|
+
function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
|
|
979
1176
|
if (!schema) return null;
|
|
980
1177
|
const inferTypeName = inferred ? resolver.resolveTypeName(name) : null;
|
|
1178
|
+
const codecRefNames = direction === "input" && !mini ? new Set(ast.collect(schema, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.extractRefName(n.ref) ?? void 0 : void 0 })) : null;
|
|
981
1179
|
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
982
|
-
name: resolver.resolveSchemaName(schemaName),
|
|
1180
|
+
name: codecRefNames?.has(schemaName) ? resolver.resolveInputSchemaName(schemaName) : resolver.resolveSchemaName(schemaName),
|
|
983
1181
|
path: resolver.resolveFile({
|
|
984
1182
|
name: schemaName,
|
|
985
1183
|
extname: ".ts"
|
|
@@ -989,8 +1187,6 @@ const zodGenerator = defineGenerator({
|
|
|
989
1187
|
group: group ?? void 0
|
|
990
1188
|
}).path
|
|
991
1189
|
}));
|
|
992
|
-
const cachedStd = zodPrinterCache.get(resolver);
|
|
993
|
-
const cachedMini = zodMiniPrinterCache.get(resolver);
|
|
994
1190
|
const schemaPrinter = mini ? keysToOmit?.length ? printerZodMini({
|
|
995
1191
|
guidType,
|
|
996
1192
|
wrapOutput,
|
|
@@ -998,10 +1194,9 @@ const zodGenerator = defineGenerator({
|
|
|
998
1194
|
keysToOmit,
|
|
999
1195
|
cyclicSchemas,
|
|
1000
1196
|
nodes: printer?.nodes
|
|
1001
|
-
}) :
|
|
1197
|
+
}) : getMiniPrinter(resolver, {
|
|
1002
1198
|
guidType,
|
|
1003
1199
|
wrapOutput,
|
|
1004
|
-
resolver,
|
|
1005
1200
|
cyclicSchemas,
|
|
1006
1201
|
nodes: printer?.nodes
|
|
1007
1202
|
}) : keysToOmit?.length ? printerZod({
|
|
@@ -1012,16 +1207,16 @@ const zodGenerator = defineGenerator({
|
|
|
1012
1207
|
resolver,
|
|
1013
1208
|
keysToOmit,
|
|
1014
1209
|
cyclicSchemas,
|
|
1015
|
-
nodes: printer?.nodes
|
|
1016
|
-
|
|
1210
|
+
nodes: printer?.nodes,
|
|
1211
|
+
direction
|
|
1212
|
+
}) : getStdPrinters(resolver, {
|
|
1017
1213
|
coercion,
|
|
1018
1214
|
guidType,
|
|
1019
1215
|
dateType,
|
|
1020
1216
|
wrapOutput,
|
|
1021
|
-
resolver,
|
|
1022
1217
|
cyclicSchemas,
|
|
1023
1218
|
nodes: printer?.nodes
|
|
1024
|
-
});
|
|
1219
|
+
})[direction];
|
|
1025
1220
|
return /* @__PURE__ */ jsxs(Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1026
1221
|
root: meta.file.path,
|
|
1027
1222
|
path: imp.path,
|
|
@@ -1037,22 +1232,48 @@ const zodGenerator = defineGenerator({
|
|
|
1037
1232
|
inferTypeName
|
|
1038
1233
|
})] });
|
|
1039
1234
|
}
|
|
1235
|
+
function buildContentTypeVariants(entries, baseName, decorate, direction) {
|
|
1236
|
+
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1237
|
+
const unionSchema = ast.createSchema({
|
|
1238
|
+
type: "union",
|
|
1239
|
+
members: variants.map((variant) => ast.createSchema({
|
|
1240
|
+
type: "ref",
|
|
1241
|
+
name: variant.name
|
|
1242
|
+
}))
|
|
1243
|
+
});
|
|
1244
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [variants.map((variant) => renderSchemaEntry({
|
|
1245
|
+
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
1246
|
+
name: variant.name,
|
|
1247
|
+
keysToOmit: variant.keysToOmit,
|
|
1248
|
+
direction
|
|
1249
|
+
})), renderSchemaEntry({
|
|
1250
|
+
schema: unionSchema,
|
|
1251
|
+
name: baseName,
|
|
1252
|
+
direction
|
|
1253
|
+
})] });
|
|
1254
|
+
}
|
|
1040
1255
|
const paramSchemas = params.map((param) => renderSchemaEntry({
|
|
1041
1256
|
schema: param.schema,
|
|
1042
|
-
name: resolver.resolveParamName(node, param)
|
|
1257
|
+
name: resolver.resolveParamName(node, param),
|
|
1258
|
+
direction: "input"
|
|
1043
1259
|
}));
|
|
1044
|
-
const responseSchemas = node.responses.map((res) =>
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1260
|
+
const responseSchemas = node.responses.map((res) => {
|
|
1261
|
+
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
1262
|
+
if (variants.length > 1) return buildContentTypeVariants(res.content, resolver.resolveResponseStatusName(node, res.statusCode));
|
|
1263
|
+
const primary = variants[0] ?? res.content?.[0];
|
|
1264
|
+
return renderSchemaEntry({
|
|
1265
|
+
schema: primary?.schema ?? null,
|
|
1266
|
+
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
1267
|
+
keysToOmit: primary?.keysToOmit
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
1050
1271
|
const responseUnionSchema = responsesWithSchema.length > 0 ? (() => {
|
|
1051
1272
|
const responseUnionName = resolver.resolveResponseName(node);
|
|
1052
|
-
if (new Set(responsesWithSchema.flatMap((res) => res.content
|
|
1273
|
+
if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
|
|
1053
1274
|
name: resolver.resolveSchemaName(schemaName),
|
|
1054
1275
|
path: ""
|
|
1055
|
-
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseUnionName)) return null;
|
|
1276
|
+
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(responseUnionName)) return null;
|
|
1056
1277
|
const members = responsesWithSchema.map((res) => ast.createSchema({
|
|
1057
1278
|
type: "ref",
|
|
1058
1279
|
name: resolver.resolveResponseStatusName(node, res.statusCode)
|
|
@@ -1065,14 +1286,27 @@ const zodGenerator = defineGenerator({
|
|
|
1065
1286
|
name: responseUnionName
|
|
1066
1287
|
});
|
|
1067
1288
|
})() : null;
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1289
|
+
const requestBodyContent = node.requestBody?.content ?? [];
|
|
1290
|
+
const requestSchema = (() => {
|
|
1291
|
+
if (requestBodyContent.length === 0) return null;
|
|
1292
|
+
if (requestBodyContent.length === 1) {
|
|
1293
|
+
const entry = requestBodyContent[0];
|
|
1294
|
+
if (!entry.schema) return null;
|
|
1295
|
+
return renderSchemaEntry({
|
|
1296
|
+
schema: {
|
|
1297
|
+
...entry.schema,
|
|
1298
|
+
description: node.requestBody.description ?? entry.schema.description
|
|
1299
|
+
},
|
|
1300
|
+
name: resolver.resolveDataName(node),
|
|
1301
|
+
keysToOmit: entry.keysToOmit,
|
|
1302
|
+
direction: "input"
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
|
|
1306
|
+
...schema,
|
|
1307
|
+
description: node.requestBody.description ?? schema.description
|
|
1308
|
+
}), "input");
|
|
1309
|
+
})();
|
|
1076
1310
|
return /* @__PURE__ */ jsxs(File, {
|
|
1077
1311
|
baseName: meta.file.baseName,
|
|
1078
1312
|
path: meta.file.path,
|
|
@@ -1119,7 +1353,7 @@ const zodGenerator = defineGenerator({
|
|
|
1119
1353
|
output,
|
|
1120
1354
|
group: group ?? void 0
|
|
1121
1355
|
}) };
|
|
1122
|
-
const transformedOperations = nodes.map((node) => {
|
|
1356
|
+
const transformedOperations = nodes.filter(ast.isHttpOperationNode).map((node) => {
|
|
1123
1357
|
return {
|
|
1124
1358
|
node,
|
|
1125
1359
|
data: buildSchemaNames(node, {
|
|
@@ -1218,6 +1452,12 @@ const resolverZod = defineResolver(() => {
|
|
|
1218
1452
|
resolveSchemaTypeName(name) {
|
|
1219
1453
|
return ensureValidVarName(pascalCase(name, { suffix: "schema" }));
|
|
1220
1454
|
},
|
|
1455
|
+
resolveInputSchemaName(name) {
|
|
1456
|
+
return this.resolveSchemaName(`${name} input`);
|
|
1457
|
+
},
|
|
1458
|
+
resolveInputSchemaTypeName(name) {
|
|
1459
|
+
return this.resolveSchemaTypeName(`${name} input`);
|
|
1460
|
+
},
|
|
1221
1461
|
resolveTypeName(name) {
|
|
1222
1462
|
return ensureValidVarName(pascalCase(name));
|
|
1223
1463
|
},
|
|
@@ -1287,13 +1527,7 @@ const pluginZod = definePlugin((options) => {
|
|
|
1287
1527
|
path: "zod",
|
|
1288
1528
|
barrelType: "named"
|
|
1289
1529
|
}, group, exclude = [], include, override = [], typed = false, operations = false, mini = false, guidType = "uuid", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, wrapOutput = void 0, paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
|
|
1290
|
-
const groupConfig = group
|
|
1291
|
-
...group,
|
|
1292
|
-
name: (ctx) => {
|
|
1293
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1294
|
-
return `${camelCase(ctx.group)}Controller`;
|
|
1295
|
-
}
|
|
1296
|
-
} : null;
|
|
1530
|
+
const groupConfig = createGroupConfig(group, { suffix: "Controller" });
|
|
1297
1531
|
return {
|
|
1298
1532
|
name: pluginZodName,
|
|
1299
1533
|
options,
|