@kubb/plugin-zod 5.2.0-canary.20260922T120902 → 5.2.1

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.d.ts CHANGED
@@ -225,6 +225,20 @@ type ResolverZod = Resolver & {
225
225
  * `resolver.schema.inputTypeName('order') // → 'OrderInputSchemaType'`
226
226
  */
227
227
  inputTypeName(name: string): string;
228
+ /**
229
+ * Resolves the type guard function name for a schema.
230
+ *
231
+ * @example Type guard names
232
+ * `resolver.schema.isName('pet') // → 'isPet'`
233
+ */
234
+ isName(name: string): string;
235
+ /**
236
+ * Resolves the assertion function name for a schema.
237
+ *
238
+ * @example Assertion names
239
+ * `resolver.schema.assertName('pet') // → 'assertPet'`
240
+ */
241
+ assertName(name: string): string;
228
242
  };
229
243
  /**
230
244
  * Names for an operation's parameters: an individual parameter and the grouped path, query, and
@@ -395,6 +409,20 @@ type Options = OutputOptions & {
395
409
  * to the default `resolverZod`.
396
410
  */
397
411
  resolver?: ResolverPatch<ResolverZod>;
412
+ /**
413
+ * Generate TypeScript type guards (`is*`) and assertion functions (`assert*`) for schemas
414
+ * using Zod v4's native `validate` API.
415
+ * - `true`: Generates both `is*` type guards and `assert*` assertion functions.
416
+ * - `{ is?: boolean; assert?: boolean }`: Selectively enable type guards or assertions.
417
+ *
418
+ * @default false
419
+ * @note Only compatible with Zod v4.6.0 or above.
420
+ * @see https://zod.dev
421
+ */
422
+ typeGuards?: boolean | {
423
+ is?: boolean;
424
+ assert?: boolean;
425
+ };
398
426
  /**
399
427
  * Replace the Zod handler for a specific schema type (`'integer'`, `'date'`, ...).
400
428
  * When `mini: true`, overrides target the Zod Mini printer instead.
@@ -424,6 +452,7 @@ type ResolvedOptions = {
424
452
  guidType: NonNullable<Options['guidType']>;
425
453
  regexType: NonNullable<Options['regexType']>;
426
454
  mini: NonNullable<Options['mini']>;
455
+ typeGuards: NonNullable<Options['typeGuards']>;
427
456
  printer: Options['printer'];
428
457
  };
429
458
  type PluginZod = PluginFactoryOptions<'plugin-zod', Options, ResolvedOptions, ResolverZod>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
2
  import { Resolver, ast, containsCircularRef, createResolver, defineGenerator, definePlugin, extractRefName, syncSchemaRef } from "kubb/kit";
3
- import { Const, File, Type, jsxRenderer } from "kubb/jsx";
3
+ import { Const, File, Function, Type, jsxRenderer } from "kubb/jsx";
4
4
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
5
5
  //#region ../../internals/shared/src/params.ts
6
6
  /**
@@ -735,31 +735,61 @@ function mapSchemaItems(node, transform) {
735
735
  }
736
736
  //#endregion
737
737
  //#region src/components/Zod.tsx
738
- function Zod({ name, node, printer, inferTypeName, cyclic }) {
738
+ function Zod({ name, node, printer, inferTypeName, typeGuards, isName, assertName, mini, cyclic }) {
739
739
  const output = printer.print(node);
740
740
  if (!output) return;
741
741
  const needsAnnotation = cyclic && node.type !== "object";
742
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Source, {
743
- name,
744
- isExportable: true,
745
- isIndexable: true,
746
- children: /* @__PURE__ */ jsx(Const, {
747
- export: true,
742
+ const targetType = inferTypeName ?? `z.infer<typeof ${name}>`;
743
+ const shouldGenerateIs = isName && (typeof typeGuards === "object" ? typeGuards.is ?? true : Boolean(typeGuards));
744
+ const shouldGenerateAssert = assertName && (typeof typeGuards === "object" ? typeGuards.assert ?? true : Boolean(typeGuards));
745
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
746
+ /* @__PURE__ */ jsx(File.Source, {
748
747
  name,
749
- type: needsAnnotation ? "z.ZodType" : void 0,
750
- children: output
751
- })
752
- }), inferTypeName && /* @__PURE__ */ jsx(File.Source, {
753
- name: inferTypeName,
754
- isExportable: true,
755
- isIndexable: true,
756
- isTypeOnly: true,
757
- children: /* @__PURE__ */ jsx(Type, {
758
- export: true,
748
+ isExportable: true,
749
+ isIndexable: true,
750
+ children: /* @__PURE__ */ jsx(Const, {
751
+ export: true,
752
+ name,
753
+ type: needsAnnotation ? "z.ZodType" : void 0,
754
+ children: output
755
+ })
756
+ }),
757
+ inferTypeName && /* @__PURE__ */ jsx(File.Source, {
759
758
  name: inferTypeName,
760
- children: `z.infer<typeof ${name}>`
759
+ isExportable: true,
760
+ isIndexable: true,
761
+ isTypeOnly: true,
762
+ children: /* @__PURE__ */ jsx(Type, {
763
+ export: true,
764
+ name: inferTypeName,
765
+ children: `z.infer<typeof ${name}>`
766
+ })
767
+ }),
768
+ shouldGenerateIs && /* @__PURE__ */ jsx(File.Source, {
769
+ name: isName,
770
+ isExportable: true,
771
+ isIndexable: true,
772
+ children: /* @__PURE__ */ jsx(Const, {
773
+ export: true,
774
+ name: isName,
775
+ JSDoc: { comments: [`Type guard for {@link ${name}}`] },
776
+ children: mini ? `(data: unknown): data is ${targetType} => z.validate(${name}, data)` : `(data: unknown): data is ${targetType} => ${name}.validate(data)`
777
+ })
778
+ }),
779
+ shouldGenerateAssert && /* @__PURE__ */ jsx(File.Source, {
780
+ name: assertName,
781
+ isExportable: true,
782
+ isIndexable: true,
783
+ children: /* @__PURE__ */ jsx(Function, {
784
+ export: true,
785
+ name: assertName,
786
+ params: "data: unknown",
787
+ returnType: `asserts data is ${targetType}`,
788
+ JSDoc: { comments: [`Asserter for {@link ${name}}`, "@throws {z.ZodError} If data is invalid"] },
789
+ children: mini ? `if (!z.validate(${name}, data)) {\n z.parse(${name}, data)\n}` : `if (!${name}.validate(data)) {\n ${name}.parse(data)\n}`
790
+ })
761
791
  })
762
- })] });
792
+ ] });
763
793
  }
764
794
  //#endregion
765
795
  //#region src/constants.ts
@@ -1034,8 +1064,20 @@ function buildGroupedParamsSchema({ params, optional }) {
1034
1064
  }
1035
1065
  //#endregion
1036
1066
  //#region src/printers/printerZod.ts
1037
- function strictOneOfMember$1(member, node, cyclicSchemas) {
1038
- if (node.type === "object" && node.additionalProperties === void 0) return `${member}.strict()`;
1067
+ /**
1068
+ * A `oneOf` member that is an inline object is exhaustive, so it prints as a strict object.
1069
+ * Marking the node lets `object()` build `z.strictObject(...)` itself; appending `.strict()` to the
1070
+ * printed member instead would read `.shape` eagerly and run a self-reference's deferred getter
1071
+ * inside the temporal dead zone.
1072
+ */
1073
+ function strictOneOfNode$1(node) {
1074
+ return node.type === "object" && node.additionalProperties === void 0 ? {
1075
+ ...node,
1076
+ additionalProperties: false
1077
+ } : node;
1078
+ }
1079
+ __name(strictOneOfNode$1, "strictOneOfNode");
1080
+ function strictOneOfMember(member, node, cyclicSchemas) {
1039
1081
  if (node.type === "ref") {
1040
1082
  if (member.startsWith("z.lazy(")) return member;
1041
1083
  const refName = ast.resolveRefName(node);
@@ -1046,7 +1088,6 @@ function strictOneOfMember$1(member, node, cyclicSchemas) {
1046
1088
  }
1047
1089
  return member;
1048
1090
  }
1049
- __name(strictOneOfMember$1, "strictOneOfMember");
1050
1091
  function getMemberConstraint({ member, regexType }) {
1051
1092
  if (member.primitive === "string") return lengthConstraints({
1052
1093
  ...ast.narrowSchema(member, "string") ?? {},
@@ -1190,6 +1231,32 @@ const scalarNodes = {
1190
1231
  boolean: () => "z.boolean()",
1191
1232
  null: () => "z.null()",
1192
1233
  string(node) {
1234
+ if (!shouldCoerce(this.options.coercion, "strings")) {
1235
+ if (node.format === "byte" || node.format === "base64") return `z.base64()${lengthConstraints({
1236
+ ...node,
1237
+ regexType: this.options.regexType
1238
+ })}`;
1239
+ if (node.format === "base64url") return `z.base64url()${lengthConstraints({
1240
+ ...node,
1241
+ regexType: this.options.regexType
1242
+ })}`;
1243
+ if (node.format === "jwt") return `z.jwt()${lengthConstraints({
1244
+ ...node,
1245
+ regexType: this.options.regexType
1246
+ })}`;
1247
+ if (node.format === "ulid") return `z.ulid()${lengthConstraints({
1248
+ ...node,
1249
+ regexType: this.options.regexType
1250
+ })}`;
1251
+ if (node.format === "iban") return `z.iban()${lengthConstraints({
1252
+ ...node,
1253
+ regexType: this.options.regexType
1254
+ })}`;
1255
+ if (node.format === "duration") return `z.iso.duration()${lengthConstraints({
1256
+ ...node,
1257
+ regexType: this.options.regexType
1258
+ })}`;
1259
+ }
1193
1260
  const base = shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()";
1194
1261
  const pattern = node.pattern ?? integerFormatPattern(node.format);
1195
1262
  return `${base}${lengthConstraints({
@@ -1202,6 +1269,10 @@ const scalarNodes = {
1202
1269
  return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${numberConstraints(node)}`;
1203
1270
  },
1204
1271
  integer(node) {
1272
+ if (!shouldCoerce(this.options.coercion, "numbers")) {
1273
+ if (node.format === "int32") return `z.int32()${numberConstraints(node)}`;
1274
+ if (node.format === "uint32") return `z.uint32()${numberConstraints(node)}`;
1275
+ }
1205
1276
  return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number().int()" : "z.int()"}${numberConstraints(node)}`;
1206
1277
  },
1207
1278
  bigint() {
@@ -1288,15 +1359,16 @@ const printerZod = ast.createPrinter((options) => {
1288
1359
  },
1289
1360
  object(node) {
1290
1361
  const entries = node.properties ?? [];
1291
- const objectBase = `z.object(${buildZodObjectShape(this, node)})`;
1362
+ const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1363
+ const isStrict = node.additionalProperties === false && patterns.length === 0;
1364
+ const objectBase = `${isStrict ? "z.strictObject" : "z.object"}(${buildZodObjectShape(this, node)})`;
1292
1365
  return (() => {
1293
- const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1294
1366
  if (node.additionalProperties && node.additionalProperties !== true) {
1295
1367
  const catchallType = this.transform(node.additionalProperties);
1296
1368
  return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1297
1369
  }
1298
1370
  if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
1299
- if (node.additionalProperties === false && patterns.length === 0) return `${objectBase}.strict()`;
1371
+ if (isStrict) return objectBase;
1300
1372
  if (patterns.length > 0) {
1301
1373
  const values = patterns.map(([, valueSchema]) => {
1302
1374
  const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
@@ -1325,7 +1397,7 @@ const printerZod = ast.createPrinter((options) => {
1325
1397
  },
1326
1398
  union(node) {
1327
1399
  const nodeMembers = node.members ?? [];
1328
- const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema, cyclicSchemaNames) : output).filter(Boolean);
1400
+ const members = mapSchemaMembers(node, (memberNode) => this.transform(node.strategy === "one" ? strictOneOfNode$1(memberNode) : memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema, cyclicSchemaNames) : output).filter(Boolean);
1329
1401
  if (members.length === 0) return "";
1330
1402
  if (members.length === 1) return members[0];
1331
1403
  const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
@@ -1379,9 +1451,15 @@ const printerZod = ast.createPrinter((options) => {
1379
1451
  });
1380
1452
  //#endregion
1381
1453
  //#region src/printers/printerZodMini.ts
1382
- function strictOneOfMember(member, node) {
1383
- if (node.type === "object" && (node.additionalProperties === void 0 || node.additionalProperties === false)) return member.replace(/^z\.object\(/, "z.strictObject(");
1384
- return member;
1454
+ /**
1455
+ * A `oneOf` member that is an inline object is exhaustive, so it prints as a strict object.
1456
+ * Marking the node lets `object()` build `z.strictObject(...)` itself, with no rewrite afterwards.
1457
+ */
1458
+ function strictOneOfNode(node) {
1459
+ return node.type === "object" && node.additionalProperties === void 0 ? {
1460
+ ...node,
1461
+ additionalProperties: false
1462
+ } : node;
1385
1463
  }
1386
1464
  function getMemberConstraintMini({ member, regexType }) {
1387
1465
  if (member.primitive === "string") return lengthChecksMini({
@@ -1451,6 +1529,30 @@ const printerZodMini = ast.createPrinter((options) => {
1451
1529
  boolean: () => "z.boolean()",
1452
1530
  null: () => "z.null()",
1453
1531
  string(node) {
1532
+ if (node.format === "byte" || node.format === "base64") return `z.base64()${lengthChecksMini({
1533
+ ...node,
1534
+ regexType: this.options.regexType
1535
+ })}`;
1536
+ if (node.format === "base64url") return `z.base64url()${lengthChecksMini({
1537
+ ...node,
1538
+ regexType: this.options.regexType
1539
+ })}`;
1540
+ if (node.format === "jwt") return `z.jwt()${lengthChecksMini({
1541
+ ...node,
1542
+ regexType: this.options.regexType
1543
+ })}`;
1544
+ if (node.format === "ulid") return `z.ulid()${lengthChecksMini({
1545
+ ...node,
1546
+ regexType: this.options.regexType
1547
+ })}`;
1548
+ if (node.format === "iban") return `z.iban()${lengthChecksMini({
1549
+ ...node,
1550
+ regexType: this.options.regexType
1551
+ })}`;
1552
+ if (node.format === "duration") return `z.iso.duration()${lengthChecksMini({
1553
+ ...node,
1554
+ regexType: this.options.regexType
1555
+ })}`;
1454
1556
  const pattern = node.pattern ?? integerFormatPattern(node.format);
1455
1557
  return `z.string()${lengthChecksMini({
1456
1558
  ...node,
@@ -1462,6 +1564,8 @@ const printerZodMini = ast.createPrinter((options) => {
1462
1564
  return `z.number()${numberChecksMini(node)}`;
1463
1565
  },
1464
1566
  integer(node) {
1567
+ if (node.format === "int32") return `z.int32()${numberChecksMini(node)}`;
1568
+ if (node.format === "uint32") return `z.uint32()${numberChecksMini(node)}`;
1465
1569
  return `z.int()${numberChecksMini(node)}`;
1466
1570
  },
1467
1571
  bigint(node) {
@@ -1518,14 +1622,15 @@ const printerZodMini = ast.createPrinter((options) => {
1518
1622
  },
1519
1623
  object(node) {
1520
1624
  const entries = node.properties ?? [];
1521
- const objectBase = `z.object(${buildZodMiniObjectShape(this, node)})`;
1522
1625
  const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1626
+ const isStrict = node.additionalProperties === false && patterns.length === 0;
1627
+ const objectBase = `${isStrict ? "z.strictObject" : "z.object"}(${buildZodMiniObjectShape(this, node)})`;
1523
1628
  if (node.additionalProperties && node.additionalProperties !== true) {
1524
1629
  const catchallType = this.transform(node.additionalProperties);
1525
1630
  return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1526
1631
  }
1527
1632
  if (node.additionalProperties === true) return `z.catchall(${objectBase}, ${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
1528
- if (node.additionalProperties === false && patterns.length === 0) return objectBase.replace(/^z\.object\(/, "z.strictObject(");
1633
+ if (isStrict) return objectBase;
1529
1634
  if (patterns.length > 0) {
1530
1635
  const values = patterns.map(([, valueSchema]) => {
1531
1636
  const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
@@ -1553,7 +1658,7 @@ const printerZodMini = ast.createPrinter((options) => {
1553
1658
  },
1554
1659
  union(node) {
1555
1660
  const nodeMembers = node.members ?? [];
1556
- const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
1661
+ const members = mapSchemaMembers(node, (memberNode) => this.transform(node.strategy === "one" ? strictOneOfNode(memberNode) : memberNode)).map(({ output }) => output).filter(Boolean);
1557
1662
  if (members.length === 0) return "";
1558
1663
  if (members.length === 1) return members[0];
1559
1664
  const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
@@ -1667,7 +1772,7 @@ const zodGenerator = defineGenerator({
1667
1772
  renderer: jsxRenderer,
1668
1773
  schema(node, ctx) {
1669
1774
  const { adapter, config, resolver, root } = ctx;
1670
- const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
1775
+ const { output, coercion, guidType, regexType, mini, inferred, typeGuards, importPath, group, printer } = ctx.options;
1671
1776
  const dateType = getOasAdapter(adapter).options.dateType;
1672
1777
  if (!node.name) return;
1673
1778
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
@@ -1723,6 +1828,8 @@ const zodGenerator = defineGenerator({
1723
1828
  })
1724
1829
  };
1725
1830
  const inferTypeName = inferred ? resolver.schema.typeName(node.name) : null;
1831
+ const isName = typeGuards ? resolver.schema.isName(node.name) : null;
1832
+ const assertName = typeGuards ? resolver.schema.assertName(node.name) : null;
1726
1833
  const stdPrinters = mini ? null : getStdPrinters(resolver, {
1727
1834
  coercion,
1728
1835
  guidType,
@@ -1777,6 +1884,10 @@ const zodGenerator = defineGenerator({
1777
1884
  node,
1778
1885
  printer: schemaPrinter,
1779
1886
  inferTypeName,
1887
+ typeGuards,
1888
+ isName,
1889
+ assertName,
1890
+ mini,
1780
1891
  cyclic: cyclicSchemas.has(node.name)
1781
1892
  }),
1782
1893
  hasDirectionalNode && stdPrinters && /* @__PURE__ */ jsx(Zod, {
@@ -1784,6 +1895,10 @@ const zodGenerator = defineGenerator({
1784
1895
  node,
1785
1896
  printer: stdPrinters.encode,
1786
1897
  inferTypeName: inferred ? resolver.schema.inputTypeName(node.name) : null,
1898
+ typeGuards,
1899
+ isName: typeGuards ? resolver.schema.isName(resolver.schema.inputName(node.name)) : null,
1900
+ assertName: typeGuards ? resolver.schema.assertName(resolver.schema.inputName(node.name)) : null,
1901
+ mini,
1787
1902
  cyclic: cyclicSchemas.has(node.name)
1788
1903
  })
1789
1904
  ]
@@ -2062,6 +2177,12 @@ const resolverZod = createResolver({
2062
2177
  },
2063
2178
  inputTypeName(name) {
2064
2179
  return this.schema.typeName(`${name} input`);
2180
+ },
2181
+ isName(name) {
2182
+ return ensureValidVarName(camelCase(name, { prefix: "is" }));
2183
+ },
2184
+ assertName(name) {
2185
+ return ensureValidVarName(camelCase(name, { prefix: "assert" }));
2065
2186
  }
2066
2187
  },
2067
2188
  param: createOperationParamResolver(),
@@ -2112,7 +2233,7 @@ const pluginZod = definePlugin((options) => {
2112
2233
  const { output = {
2113
2234
  path: "zod",
2114
2235
  barrel: { type: "named" }
2115
- }, group, exclude = [], include, override = [], mini = false, guidType = "uuid", regexType = "literal", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, printer, resolver: userResolver, macros: userMacros } = options;
2236
+ }, group, exclude = [], include, override = [], mini = false, guidType = "uuid", regexType = "literal", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, typeGuards = false, printer, resolver: userResolver, macros: userMacros } = options;
2116
2237
  const groupConfig = createGroupConfig(group);
2117
2238
  return {
2118
2239
  name: pluginZodName,
@@ -2130,6 +2251,7 @@ const pluginZod = definePlugin((options) => {
2130
2251
  guidType,
2131
2252
  regexType,
2132
2253
  mini,
2254
+ typeGuards,
2133
2255
  printer
2134
2256
  });
2135
2257
  ctx.setResolver(userResolver ? Resolver.merge(resolverZod, userResolver) : resolverZod);