@kubb/plugin-zod 5.0.0-beta.77 → 5.0.0-beta.79

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 CHANGED
@@ -82,7 +82,7 @@ function toFilePath(name, caseLast = camelCase) {
82
82
  * JavaScript and Java reserved words.
83
83
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
84
84
  */
85
- const reservedWords = new Set([
85
+ const reservedWords = /* @__PURE__ */ new Set([
86
86
  "abstract",
87
87
  "arguments",
88
88
  "boolean",
@@ -314,9 +314,10 @@ function createGroupConfig(group) {
314
314
  }
315
315
  //#endregion
316
316
  //#region src/components/Zod.tsx
317
- function Zod({ name, node, printer, inferTypeName }) {
317
+ function Zod({ name, node, printer, inferTypeName, cyclic }) {
318
318
  const output = printer.print(node);
319
319
  if (!output) return;
320
+ const needsAnnotation = cyclic && node.type !== "object";
320
321
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
321
322
  name,
322
323
  isExportable: true,
@@ -324,6 +325,7 @@ function Zod({ name, node, printer, inferTypeName }) {
324
325
  children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Const, {
325
326
  export: true,
326
327
  name,
328
+ type: needsAnnotation ? "z.ZodType" : void 0,
327
329
  children: output
328
330
  })
329
331
  }), inferTypeName && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
@@ -344,7 +346,7 @@ function Zod({ name, node, printer, inferTypeName }) {
344
346
  * Import paths that use a namespace import (`import * as z from '...'`).
345
347
  * All other import paths use a named import (`import { z } from '...'`).
346
348
  */
347
- const ZOD_NAMESPACE_IMPORTS = new Set(["zod", "zod/mini"]);
349
+ const ZOD_NAMESPACE_IMPORTS = /* @__PURE__ */ new Set(["zod", "zod/mini"]);
348
350
  //#endregion
349
351
  //#region src/utils.ts
350
352
  /**
@@ -427,6 +429,33 @@ function formatDefault(value) {
427
429
  return String(value ?? "");
428
430
  }
429
431
  /**
432
+ * Format a default for `.default(...)` so the literal matches the generated schema's type.
433
+ *
434
+ * An OpenAPI `default` does not always agree with the schema it sits on: a `bigint` field
435
+ * (`format: int64`) carries a `number` default, and a spec may put `default: {}` on an `array`.
436
+ * Emitting those verbatim produces a Zod schema that does not typecheck, so coerce the bigint case
437
+ * to a `BigInt(...)` literal and emit an array literal for arrays (dropping a non-array default).
438
+ * Returns `null` when no `.default(...)` should be emitted. Keys off the schema node's type.
439
+ */
440
+ function defaultLiteral(node, value) {
441
+ if (value === null) return null;
442
+ if (node && _kubb_core.ast.narrowSchema(node, "bigint")) {
443
+ if (typeof value === "bigint") return `BigInt(${value})`;
444
+ if (typeof value === "number" && Number.isInteger(value)) return `BigInt(${value})`;
445
+ return null;
446
+ }
447
+ if (node && _kubb_core.ast.narrowSchema(node, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
448
+ const enumNode = node ? _kubb_core.ast.narrowSchema(node, "enum") : void 0;
449
+ if (enumNode) {
450
+ const values = enumNode.namedEnumValues?.map((member) => member.value) ?? enumNode.enumValues ?? [];
451
+ if (values.length) {
452
+ const match = values.find((member) => member === value || String(member) === String(value));
453
+ return match != null ? formatLiteral(match) : null;
454
+ }
455
+ }
456
+ return formatDefault(value);
457
+ }
458
+ /**
430
459
  * Format a primitive enum/literal value.
431
460
  * Strings are quoted; numbers and booleans are emitted raw.
432
461
  */
@@ -435,6 +464,18 @@ function formatLiteral(v) {
435
464
  return String(v);
436
465
  }
437
466
  /**
467
+ * Build the Zod schema for a set of literal enum values.
468
+ * `z.enum()` only accepts string members in Zod v4, so a numeric, boolean, or
469
+ * mixed set is emitted as a single `z.literal(…)` or a `z.union([z.literal(…), …])`.
470
+ * An all-string set keeps the more compact `z.enum([…])`.
471
+ */
472
+ function buildEnum(values) {
473
+ if (values.every((v) => typeof v === "string")) return `z.enum([${values.map(formatLiteral).join(", ")}])`;
474
+ const literals = values.map((v) => `z.literal(${formatLiteral(v)})`);
475
+ if (literals.length === 1) return literals[0];
476
+ return `z.union([${literals.join(", ")}])`;
477
+ }
478
+ /**
438
479
  * Map a `regexType` to the `func` argument of `toRegExpString`: `'constructor'` emits
439
480
  * `new RegExp(...)`, while `'literal'` (the default) emits a regex literal.
440
481
  */
@@ -513,36 +554,62 @@ function lengthChecksMini({ min, max, pattern, regexType }) {
513
554
  * Apply nullable / optional / nullish modifiers, an optional `.describe()` call, and an
514
555
  * optional `.meta({ examples })` call to a schema value string using the chainable Zod v4 API.
515
556
  */
516
- function applyModifiers({ value, nullable, optional, nullish, defaultValue, description, examples }) {
557
+ function applyModifiers({ value, schema, nullable, optional, nullish, defaultValue, description, examples }) {
517
558
  const withModifier = (() => {
518
559
  if (nullish || nullable && optional) return `${value}.nullish()`;
519
560
  if (optional) return `${value}.optional()`;
520
561
  if (nullable) return `${value}.nullable()`;
521
562
  return value;
522
563
  })();
523
- const withDefault = defaultValue !== void 0 ? `${withModifier}.default(${formatDefault(defaultValue)})` : withModifier;
564
+ const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
565
+ const withDefault = literal !== null ? `${withModifier}.default(${literal})` : withModifier;
524
566
  const withDescription = description ? `${withDefault}.describe(${(0, _kubb_ast_utils.stringify)(description)})` : withDefault;
525
567
  return examples?.length ? `${withDescription}.meta({ examples: [${examples.map(formatDefault).join(", ")}] })` : withDescription;
526
568
  }
569
+ function modifierDepth(schema) {
570
+ if (schema.nullish || schema.nullable && schema.optional) return 2;
571
+ if (schema.nullable || schema.optional) return 1;
572
+ return 0;
573
+ }
574
+ /**
575
+ * Build the `.unwrap()` chain to insert before `.omit()` when the schema is a `$ref`.
576
+ *
577
+ * A `$ref` resolves to a named schema variable that already carries its own `.nullable()` /
578
+ * `.optional()` / `.nullish()` / `.default()` wrappers. `.omit()` lives on the inner `ZodObject`,
579
+ * not on those `ZodNullable` / `ZodOptional` / `ZodDefault` wrappers, so the omit has to unwrap
580
+ * down to the object first. This mirrors plugin-ts emitting `Omit<NonNullable<T>, …>`. Inline
581
+ * objects need no unwrap because the printer adds their modifiers after `.omit()`.
582
+ */
583
+ function omitUnwrapChain(node) {
584
+ const ref = _kubb_core.ast.narrowSchema(node, "ref");
585
+ if (!ref) return "";
586
+ const target = ref.schema ?? ref;
587
+ const depth = modifierDepth(target) + (target.default !== void 0 ? 1 : 0);
588
+ return ".unwrap()".repeat(depth);
589
+ }
527
590
  /**
528
591
  * Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
529
592
  * (`z.nullable()`, `z.optional()`, `z.nullish()`).
530
593
  */
531
- function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }) {
594
+ function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaultValue }) {
532
595
  const withModifier = (() => {
533
596
  if (nullish) return `z.nullish(${value})`;
534
597
  const withNullable = nullable ? `z.nullable(${value})` : value;
535
598
  return optional ? `z.optional(${withNullable})` : withNullable;
536
599
  })();
537
- return defaultValue !== void 0 ? `z._default(${withModifier}, ${formatDefault(defaultValue)})` : withModifier;
600
+ const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
601
+ return literal !== null ? `z._default(${withModifier}, ${literal})` : withModifier;
538
602
  }
539
603
  //#endregion
540
604
  //#region src/printers/printerZod.ts
541
- function strictOneOfMember$1(member, node) {
605
+ function strictOneOfMember$1(member, node, cyclicSchemas) {
542
606
  if (node.type === "object" && node.additionalProperties === void 0) return `${member}.strict()`;
543
607
  if (node.type === "ref") {
544
608
  if (member.startsWith("z.lazy(")) return member;
609
+ const refName = (node.ref ? (0, _kubb_ast_utils.extractRefName)(node.ref) : void 0) ?? node.name;
610
+ if (refName && cyclicSchemas?.has(refName)) return member;
545
611
  const schema = (0, _kubb_ast_utils.syncSchemaRef)(node);
612
+ if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
546
613
  if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
547
614
  }
548
615
  return member;
@@ -572,6 +639,7 @@ function getMemberConstraint({ member, regexType }) {
572
639
  * ```
573
640
  */
574
641
  const printerZod = _kubb_core.ast.createPrinter((options) => {
642
+ const cyclicSchemaNames = options.cyclicSchemas;
575
643
  return {
576
644
  name: "zod",
577
645
  options,
@@ -641,11 +709,11 @@ const printerZod = _kubb_core.ast.createPrinter((options) => {
641
709
  if (literals.length === 1) return literals[0];
642
710
  return `z.union([${literals.join(", ")}])`;
643
711
  }
644
- return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
712
+ return buildEnum(nonNullValues);
645
713
  },
646
714
  ref(node) {
647
715
  if (!node.name) return null;
648
- const refName = node.ref ? (0, _kubb_ast_utils.extractRefName)(node.ref) ?? node.name : node.name;
716
+ const refName = node.ref ? this.options.nameMapping?.get(node.ref) ?? (0, _kubb_ast_utils.extractRefName)(node.ref) ?? node.name : node.name;
649
717
  const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
650
718
  const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.resolveInputSchemaName(refName) ?? refName : this.options.resolver?.default(refName, "function") ?? refName : node.name;
651
719
  if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
@@ -670,6 +738,7 @@ const printerZod = _kubb_core.ast.createPrinter((options) => {
670
738
  const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
671
739
  const value = applyModifiers({
672
740
  value: wrappedOutput,
741
+ schema,
673
742
  nullable: meta.nullable,
674
743
  optional: schema.optional || property.required === false,
675
744
  nullish: schema.nullish,
@@ -719,10 +788,11 @@ const printerZod = _kubb_core.ast.createPrinter((options) => {
719
788
  },
720
789
  union(node) {
721
790
  const nodeMembers = node.members ?? [];
722
- const members = (0, _kubb_ast_utils.mapSchemaMembers)(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema) : output).filter(Boolean);
791
+ const members = (0, _kubb_ast_utils.mapSchemaMembers)(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema, cyclicSchemaNames) : output).filter(Boolean);
723
792
  if (members.length === 0) return "";
724
793
  if (members.length === 1) return members[0];
725
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${(0, _kubb_ast_utils.stringify)(node.discriminatorPropertyName)}, ${(0, _kubb_ast_utils.buildList)(members)})`;
794
+ const allDiscriminable = nodeMembers.every((m) => (m.type === "ref" ? (0, _kubb_ast_utils.syncSchemaRef)(m) : m).type !== "intersection");
795
+ if (node.discriminatorPropertyName && allDiscriminable) return `z.discriminatedUnion(${(0, _kubb_ast_utils.stringify)(node.discriminatorPropertyName)}, ${(0, _kubb_ast_utils.buildList)(members)})`;
726
796
  return `z.union(${(0, _kubb_ast_utils.buildList)(members)})`;
727
797
  },
728
798
  intersection(node) {
@@ -752,10 +822,13 @@ const printerZod = _kubb_core.ast.createPrinter((options) => {
752
822
  return applyModifiers({
753
823
  value: (() => {
754
824
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
825
+ const unwrap = omitUnwrapChain(node);
826
+ const omit = `.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
755
827
  const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
756
- if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
757
- return `${transformed}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
828
+ if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}${unwrap}${omit})`;
829
+ return `${transformed}${unwrap}${omit}`;
758
830
  })(),
831
+ schema: node,
759
832
  nullable: meta.nullable,
760
833
  optional: meta.optional,
761
834
  nullish: meta.nullish,
@@ -860,11 +933,11 @@ const printerZodMini = _kubb_core.ast.createPrinter((options) => {
860
933
  if (literals.length === 1) return literals[0];
861
934
  return `z.union([${literals.join(", ")}])`;
862
935
  }
863
- return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
936
+ return buildEnum(nonNullValues);
864
937
  },
865
938
  ref(node) {
866
939
  if (!node.name) return null;
867
- const refName = node.ref ? (0, _kubb_ast_utils.extractRefName)(node.ref) ?? node.name : node.name;
940
+ const refName = node.ref ? this.options.nameMapping?.get(node.ref) ?? (0, _kubb_ast_utils.extractRefName)(node.ref) ?? node.name : node.name;
868
941
  const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
869
942
  if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
870
943
  return resolvedName;
@@ -886,6 +959,7 @@ const printerZodMini = _kubb_core.ast.createPrinter((options) => {
886
959
  output: baseOutput,
887
960
  schema
888
961
  }) || baseOutput : baseOutput,
962
+ schema,
889
963
  nullable: meta.nullable,
890
964
  optional: schema.optional || property.required === false,
891
965
  nullish: schema.nullish,
@@ -934,7 +1008,8 @@ const printerZodMini = _kubb_core.ast.createPrinter((options) => {
934
1008
  const members = (0, _kubb_ast_utils.mapSchemaMembers)(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
935
1009
  if (members.length === 0) return "";
936
1010
  if (members.length === 1) return members[0];
937
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${(0, _kubb_ast_utils.stringify)(node.discriminatorPropertyName)}, ${(0, _kubb_ast_utils.buildList)(members)})`;
1011
+ const allDiscriminable = nodeMembers.every((m) => (m.type === "ref" ? (0, _kubb_ast_utils.syncSchemaRef)(m) : m).type !== "intersection");
1012
+ if (node.discriminatorPropertyName && allDiscriminable) return `z.discriminatedUnion(${(0, _kubb_ast_utils.stringify)(node.discriminatorPropertyName)}, ${(0, _kubb_ast_utils.buildList)(members)})`;
938
1013
  return `z.union(${(0, _kubb_ast_utils.buildList)(members)})`;
939
1014
  },
940
1015
  intersection(node) {
@@ -964,10 +1039,13 @@ const printerZodMini = _kubb_core.ast.createPrinter((options) => {
964
1039
  return applyMiniModifiers({
965
1040
  value: (() => {
966
1041
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
1042
+ const unwrap = omitUnwrapChain(node);
1043
+ const omit = `.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
967
1044
  const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
968
- if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
969
- return `${transformed}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
1045
+ if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}${unwrap}${omit})`;
1046
+ return `${transformed}${unwrap}${omit}`;
970
1047
  })(),
1048
+ schema: node,
971
1049
  nullable: meta.nullable,
972
1050
  optional: meta.optional,
973
1051
  nullish: meta.nullish,
@@ -1089,6 +1167,7 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1089
1167
  })
1090
1168
  };
1091
1169
  const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null;
1170
+ const nameMapping = adapter.options.nameMapping;
1092
1171
  const stdPrinters = mini ? null : getStdPrinters(resolver, {
1093
1172
  coercion,
1094
1173
  guidType,
@@ -1096,6 +1175,7 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1096
1175
  dateType,
1097
1176
  wrapOutput,
1098
1177
  cyclicSchemas,
1178
+ nameMapping,
1099
1179
  nodes: printer?.nodes
1100
1180
  });
1101
1181
  const schemaPrinter = mini ? getMiniPrinter(resolver, {
@@ -1103,6 +1183,7 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1103
1183
  regexType,
1104
1184
  wrapOutput,
1105
1185
  cyclicSchemas,
1186
+ nameMapping,
1106
1187
  nodes: printer?.nodes
1107
1188
  }) : stdPrinters.output;
1108
1189
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
@@ -1144,13 +1225,15 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1144
1225
  name: meta.name,
1145
1226
  node,
1146
1227
  printer: schemaPrinter,
1147
- inferTypeName
1228
+ inferTypeName,
1229
+ cyclic: cyclicSchemas.has(node.name)
1148
1230
  }),
1149
1231
  hasCodec && stdPrinters && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Zod, {
1150
1232
  name: resolver.resolveInputSchemaName(node.name),
1151
1233
  node,
1152
1234
  printer: stdPrinters.input,
1153
- inferTypeName: inferred ? resolver.resolveInputSchemaTypeName(node.name) : null
1235
+ inferTypeName: inferred ? resolver.resolveInputSchemaTypeName(node.name) : null,
1236
+ cyclic: cyclicSchemas.has(node.name)
1154
1237
  })
1155
1238
  ]
1156
1239
  });
@@ -1173,6 +1256,7 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1173
1256
  group: group ?? void 0
1174
1257
  }) };
1175
1258
  const cyclicSchemas = new Set(ctx.meta.circularNames);
1259
+ const nameMapping = adapter.options.nameMapping;
1176
1260
  function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
1177
1261
  if (!schema) return null;
1178
1262
  const inferTypeName = inferred ? resolver.resolveTypeName(name) : null;
@@ -1195,12 +1279,14 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1195
1279
  resolver,
1196
1280
  keysToOmit,
1197
1281
  cyclicSchemas,
1282
+ nameMapping,
1198
1283
  nodes: printer?.nodes
1199
1284
  }) : getMiniPrinter(resolver, {
1200
1285
  guidType,
1201
1286
  regexType,
1202
1287
  wrapOutput,
1203
1288
  cyclicSchemas,
1289
+ nameMapping,
1204
1290
  nodes: printer?.nodes
1205
1291
  }) : keysToOmit?.length ? printerZod({
1206
1292
  coercion,
@@ -1211,6 +1297,7 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1211
1297
  resolver,
1212
1298
  keysToOmit,
1213
1299
  cyclicSchemas,
1300
+ nameMapping,
1214
1301
  nodes: printer?.nodes,
1215
1302
  direction
1216
1303
  }) : getStdPrinters(resolver, {
@@ -1220,6 +1307,7 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1220
1307
  dateType,
1221
1308
  wrapOutput,
1222
1309
  cyclicSchemas,
1310
+ nameMapping,
1223
1311
  nodes: printer?.nodes
1224
1312
  })[direction];
1225
1313
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [imports.map((imp) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -1234,7 +1322,8 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1234
1322
  name,
1235
1323
  node: schema,
1236
1324
  printer: schemaPrinter,
1237
- inferTypeName
1325
+ inferTypeName,
1326
+ cyclic: false
1238
1327
  })] });
1239
1328
  }
1240
1329
  function buildContentTypeVariants(entries, baseName, decorate, direction) {
@@ -1454,7 +1543,7 @@ const pluginZodName = "plugin-zod";
1454
1543
  * Generates Zod v4 schemas from an OpenAPI spec. Use them to validate API
1455
1544
  * responses at runtime, build form schemas, or feed back into router libraries
1456
1545
  * that consume Zod (tRPC, Hono, Elysia). Pair with `@kubb/plugin-axios` or
1457
- * `@kubb/plugin-fetch` and set the client's `parser: 'zod'` to validate every response
1546
+ * `@kubb/plugin-fetch` and set the client's `validator: 'zod'` to validate every response
1458
1547
  * automatically.
1459
1548
  *
1460
1549
  * @example