@kubb/plugin-zod 5.8.0 → 5.8.2

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.js CHANGED
@@ -52,6 +52,22 @@ function resolveContentTypeVariants(entries, baseName) {
52
52
  }
53
53
  const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
54
54
  /**
55
+ * An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
56
+ * per property, typed and serialized like any other.
57
+ * Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
58
+ */
59
+ function spreadQuerystring(param) {
60
+ const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
61
+ const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
62
+ if (schema?.type !== "object" || !formEncoded) return [param];
63
+ return schema.properties.map((property) => ast.factory.createParameter({
64
+ name: property.name,
65
+ in: "query",
66
+ schema: property.schema,
67
+ required: param.required && property.required
68
+ }));
69
+ }
70
+ /**
55
71
  * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
56
72
  * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
57
73
  * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
@@ -60,17 +76,18 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
60
76
  function getOperationParameters(node) {
61
77
  const cached = operationParameterGroupsByNode.get(node);
62
78
  if (cached) return cached;
63
- const group = (location) => {
79
+ const dedupe = (params) => {
64
80
  const seen = /* @__PURE__ */ new Set();
65
- return node.parameters.filter((param) => {
66
- if (param.in !== location || seen.has(param.name)) return false;
81
+ return params.filter((param) => {
82
+ if (seen.has(param.name)) return false;
67
83
  seen.add(param.name);
68
84
  return true;
69
85
  });
70
86
  };
87
+ const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
71
88
  const groups = {
72
89
  path: group("path"),
73
- query: group("query"),
90
+ query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
74
91
  header: group("header"),
75
92
  cookie: group("cookie")
76
93
  };
@@ -1259,6 +1276,37 @@ function printWithOmit(ctx, node, modify, mini = false) {
1259
1276
  }
1260
1277
  // src/printers/zodNodes.ts
1261
1278
  /**
1279
+ * Reads a union of bare single-value enums as one enum, except under `xor` or an enum override, which need each member.
1280
+ * Only a numeric union with a `format` coerces, because it printed as `z.coerce.number()` before the adapter kept its values.
1281
+ */
1282
+ function getLiteralUnion({ node, options }) {
1283
+ if (node.strategy === "one" && options.oneOfType === "xor" || options.nodes?.enum) return void 0;
1284
+ const values = (node.members ?? []).map((member) => {
1285
+ const enumNode = ast.narrowSchema(member, ast.schemaTypes.enum);
1286
+ return enumNode && !enumNode.name && !enumNode.nullable && !enumNode.namedEnumValues && enumNode.enumValues?.length === 1 ? enumNode.enumValues[0] : void 0;
1287
+ });
1288
+ if (!values.length || !values.every((value) => value !== void 0 && value !== null)) return void 0;
1289
+ return {
1290
+ values,
1291
+ coerce: !!node.format && values.every((value) => typeof value === "number") && shouldCoerce(options.coercion, "numbers")
1292
+ };
1293
+ }
1294
+ /**
1295
+ * Whether a discriminated union member's discriminator property prints as a coerced literal union.
1296
+ */
1297
+ function hasCoercedDiscriminator({ member, propertyName, options }) {
1298
+ return ast.resolveSchemaProperties({
1299
+ node: member,
1300
+ propertyName
1301
+ }).some(({ schema }) => {
1302
+ const resolved = syncSchemaRef(schema);
1303
+ return resolved.type === "union" && !!getLiteralUnion({
1304
+ node: resolved,
1305
+ options
1306
+ })?.coerce;
1307
+ });
1308
+ }
1309
+ /**
1262
1310
  * Builds the scalar handlers both dialects share. They never recurse into children. The temporal
1263
1311
  * scalars and `bigint` differ per dialect, so each printer adds its own.
1264
1312
  */
@@ -1397,8 +1445,21 @@ function buildZodNodes(dialect, cyclicSchemas) {
1397
1445
  return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
1398
1446
  },
1399
1447
  union(node) {
1448
+ const literalUnion = getLiteralUnion({
1449
+ node,
1450
+ options: this.options
1451
+ });
1452
+ if (literalUnion) {
1453
+ const schema = buildEnum(literalUnion.values);
1454
+ return literalUnion.coerce ? dialect.coercedNumber(schema) : schema;
1455
+ }
1400
1456
  const nodeMembers = node.members ?? [];
1401
- const isDiscriminated = !!node.discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemas));
1457
+ const { discriminatorPropertyName } = node;
1458
+ const isDiscriminated = !!discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemas)) && !nodeMembers.some((m) => hasCoercedDiscriminator({
1459
+ member: m,
1460
+ propertyName: discriminatorPropertyName,
1461
+ options: this.options
1462
+ }));
1402
1463
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1403
1464
  const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1404
1465
  const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ schema, output }) => output && isStrict && dialect.strictMember ? dialect.strictMember(output, schema, cyclicSchemas) : output).filter(Boolean);
@@ -1584,6 +1645,7 @@ const standardDialect = {
1584
1645
  patternKey: patternKeySchema,
1585
1646
  objectShape: buildZodObjectShape,
1586
1647
  coercedInt: "z.coerce.number().int()",
1648
+ coercedNumber: (schema) => `z.coerce.number().pipe(${schema})`,
1587
1649
  catchall: (object, value) => `${object}.catchall(${value})`,
1588
1650
  nullable: (value) => `${value}.nullable()`,
1589
1651
  unique: (array) => `${array}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`,
@@ -1692,6 +1754,7 @@ const miniDialect = {
1692
1754
  patternKey: patternKeySchemaMini,
1693
1755
  objectShape: buildZodMiniObjectShape,
1694
1756
  coercedInt: "z.coerce.number().check(z.int())",
1757
+ coercedNumber: (schema) => `z.pipe(z.coerce.number(), ${schema})`,
1695
1758
  catchall: (object, value) => `z.catchall(${object}, ${value})`,
1696
1759
  nullable: (value) => `z.nullable(${value})`,
1697
1760
  unique: (array) => `${array}.check(z.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" }))`,
@@ -2118,11 +2181,11 @@ const zodGenerator = defineGenerator({
2118
2181
  });
2119
2182
  // kubb-extension.json
2120
2183
  var compatibility = {
2121
- "kubb": ">=5.14.6",
2184
+ "kubb": ">=5.16.0",
2122
2185
  "node": ">=22.22"
2123
2186
  };
2124
2187
  // package.json
2125
- var version = "5.8.0";
2188
+ var version = "5.8.2";
2126
2189
  // src/resolvers/resolverZod.ts
2127
2190
  /**
2128
2191
  * Default resolver used by `@kubb/plugin-zod`. Decides the names and file