@kubb/plugin-zod 5.1.0 → 5.1.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.d.ts CHANGED
@@ -73,14 +73,18 @@ type PrinterZodOptions = {
73
73
  */
74
74
  cyclicSchemas?: ReadonlySet<string>;
75
75
  /**
76
- * Print direction for `dateType: 'date'` fields (`Date` in TypeScript):
77
- * - `'output'` (default): decode the wire `string` into a `Date` (response bodies).
78
- * - `'input'`: encode a `Date` back into the wire `string` (request bodies/params).
76
+ * Which way a node converts between its wire type and its runtime type:
77
+ * - `'decode'` (default): wire into runtime, used by response schemas.
78
+ * - `'encode'`: runtime back to wire, used by request bodies and parameters.
79
79
  *
80
- * Diverging the directions requires the generator to emit an `${name}InputSchema`
81
- * variant for each date-bearing component.
80
+ * Named for the conversion rather than the slot, since Zod's own `z.input` and `z.output`
81
+ * describe a different axis and read inverted here: the `'decode'` schema is the one whose
82
+ * `z.input` is the wire type.
83
+ *
84
+ * A handler returning different output per direction makes the generator emit an
85
+ * `${name}InputSchema` variant for that component.
82
86
  */
83
- direction?: 'input' | 'output';
87
+ direction?: 'encode' | 'decode';
84
88
  /**
85
89
  * Custom handler map for node type overrides.
86
90
  */
@@ -351,6 +355,9 @@ type Options = OutputOptions & {
351
355
  * validate with `z.coerce.date()` instead of the string-to-Date codec. Fields
352
356
  * kept as ISO strings (`z.iso.date()`, `z.iso.datetime()`) are never coerced.
353
357
  *
358
+ * `bigint` fields (`format: int64`) always coerce, regardless of this option:
359
+ * `JSON.parse` hands back a `number`, which a plain `z.bigint()` rejects.
360
+ *
354
361
  * @default false
355
362
  * @see https://zod.dev/?id=coercion-for-primitives
356
363
  */
@@ -391,6 +398,11 @@ type Options = OutputOptions & {
391
398
  /**
392
399
  * Replace the Zod handler for a specific schema type (`'integer'`, `'date'`, ...).
393
400
  * When `mini: true`, overrides target the Zod Mini printer instead.
401
+ *
402
+ * A handler that returns a different expression per `this.options.direction` declares a two-way
403
+ * conversion, such as a `time` field carried as an ISO string but modeled as a
404
+ * `Temporal.PlainTime`. The generator then emits an `${name}InputSchema` variant for request
405
+ * bodies to resolve to, `$ref` included. Ignore `direction` and only the printed output changes.
394
406
  */
395
407
  printer?: {
396
408
  nodes?: PrinterZodNodes | PrinterZodMiniNodes;
package/dist/index.js CHANGED
@@ -779,68 +779,6 @@ function shouldCoerce(coercion, type) {
779
779
  return !!coercion[type];
780
780
  }
781
781
  /**
782
- * Registered codecs, checked in order.
783
- */
784
- const codecs = [{
785
- matches(node) {
786
- return node.type === "date" && node.representation === "date";
787
- },
788
- decode(node) {
789
- return node.format === "date" ? "z.iso.date().transform((value) => new Date(value))" : "z.iso.datetime().transform((value) => new Date(value))";
790
- },
791
- encode(node) {
792
- return node.format === "date" ? "z.date().transform((value) => value.toISOString().slice(0, 10))" : "z.date().transform((value) => value.toISOString())";
793
- }
794
- }];
795
- /**
796
- * Returns the codec for this node, or `undefined` when the node needs no
797
- * encode/decode (its wire and runtime types match).
798
- */
799
- function getCodec(node) {
800
- if (!node) return void 0;
801
- return codecs.find((codec) => codec.matches(node));
802
- }
803
- /**
804
- * Returns `true` when the node itself is encoded/decoded by a codec.
805
- */
806
- function hasCodec(node) {
807
- return getCodec(node) !== void 0;
808
- }
809
- /**
810
- * Returns `true` when the schema transitively contains a codec node —
811
- * a value whose runtime type differs from its wire type (see {@link hasCodec}),
812
- * so it must be decoded (response) or encoded (request) at the validation boundary.
813
- * `$ref`s are followed via their resolved schema; a `seen` set guards cycles.
814
- */
815
- function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
816
- if (!node) return false;
817
- if (hasCodec(node)) return true;
818
- if (node.type === "ref") {
819
- if (!node.ref) return false;
820
- const refName = extractRefName(node.ref);
821
- if (refName) {
822
- if (seen.has(refName)) return false;
823
- seen.add(refName);
824
- }
825
- const resolved = syncSchemaRef(node);
826
- if (resolved.type === "ref") return false;
827
- return containsCodec(resolved, seen);
828
- }
829
- const children = [];
830
- if ("properties" in node && node.properties) children.push(...node.properties.map((prop) => prop.schema));
831
- if ("items" in node && node.items) children.push(...node.items);
832
- if ("members" in node && node.members) children.push(...node.members);
833
- if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
834
- return children.some((child) => containsCodec(child, seen));
835
- }
836
- /**
837
- * Collects the names of `$ref` schemas that transitively contain a codec, so the generator can route
838
- * them to their input (encode) variant.
839
- */
840
- function collectCodecRefNames(node) {
841
- return ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.resolveRefName(n) ?? void 0 : void 0 });
842
- }
843
- /**
844
782
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
845
783
  * argument. A catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
846
784
  * on `.and(…)`.
@@ -882,10 +820,12 @@ function isObjectComposableIntersection(node, cyclicSchemas) {
882
820
  }
883
821
  /**
884
822
  * Format a default value as a code-level literal.
885
- * Objects become `{}`, primitives become their string representation, strings are quoted.
823
+ * Arrays keep their contents, other objects become `{}`, primitives become their string
824
+ * representation, strings are quoted.
886
825
  */
887
826
  function formatDefault(value) {
888
827
  if (typeof value === "string") return stringify(value);
828
+ if (Array.isArray(value)) return JSON.stringify(value);
889
829
  if (typeof value === "object" && value !== null) return "{}";
890
830
  return String(value ?? "");
891
831
  }
@@ -900,13 +840,14 @@ function formatDefault(value) {
900
840
  */
901
841
  function defaultLiteral(node, value) {
902
842
  if (value === null) return null;
903
- if (node && ast.narrowSchema(node, "bigint")) {
843
+ const resolved = node ? syncSchemaRef(node) : void 0;
844
+ if (resolved && ast.narrowSchema(resolved, "bigint")) {
904
845
  if (typeof value === "bigint") return `BigInt(${value})`;
905
846
  if (typeof value === "number" && Number.isInteger(value)) return `BigInt(${value})`;
906
847
  return null;
907
848
  }
908
- if (node && ast.narrowSchema(node, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
909
- const enumNode = node ? ast.narrowSchema(node, "enum") : void 0;
849
+ if (resolved && ast.narrowSchema(resolved, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
850
+ const enumNode = resolved ? ast.narrowSchema(resolved, "enum") : void 0;
910
851
  if (enumNode) {
911
852
  const values = enumNode.namedEnumValues?.map((member) => member.value) ?? enumNode.enumValues ?? [];
912
853
  if (values.length) {
@@ -1153,6 +1094,168 @@ function buildZodObjectShape(ctx, node) {
1153
1094
  }));
1154
1095
  }
1155
1096
  /**
1097
+ * Types the direction probe skips. They delegate to their children rather than reading
1098
+ * `direction` themselves, so {@link containsDirectionalNode} walks into the children instead.
1099
+ */
1100
+ const CONTAINER_TYPES = /* @__PURE__ */ new Set([
1101
+ "object",
1102
+ "array",
1103
+ "tuple",
1104
+ "union",
1105
+ "intersection",
1106
+ "ref"
1107
+ ]);
1108
+ /**
1109
+ * Runs the node's effective handler (a `printer.nodes` override, else the built-in) once per
1110
+ * direction and reports whether the two disagree. That difference is what makes a component
1111
+ * need an `${name}InputSchema` variant.
1112
+ */
1113
+ function variesByDirection({ node, printerOptions }) {
1114
+ if (CONTAINER_TYPES.has(node.type)) return false;
1115
+ const handler = printerOptions.nodes?.[node.type] ?? scalarNodes[node.type];
1116
+ if (!handler) return false;
1117
+ const call = (direction) => {
1118
+ const context = {
1119
+ options: {
1120
+ ...printerOptions,
1121
+ direction
1122
+ },
1123
+ transform: () => null,
1124
+ base: () => null
1125
+ };
1126
+ return handler.call(context, node);
1127
+ };
1128
+ return call("decode") !== call("encode");
1129
+ }
1130
+ /**
1131
+ * Whether the schema transitively contains a node that prints differently per direction, so it
1132
+ * must decode on responses and encode on requests. Follows `$ref`s through their resolved
1133
+ * schema, with `seen` guarding cycles.
1134
+ */
1135
+ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */ new Set() }) {
1136
+ if (!node) return false;
1137
+ if (node.type === "ref") {
1138
+ if (!node.ref) return false;
1139
+ const refName = extractRefName(node.ref);
1140
+ if (refName) {
1141
+ if (seen.has(refName)) return false;
1142
+ seen.add(refName);
1143
+ }
1144
+ const resolved = syncSchemaRef(node);
1145
+ if (resolved.type === "ref") return false;
1146
+ return containsDirectionalNode({
1147
+ node: resolved,
1148
+ printerOptions,
1149
+ seen
1150
+ });
1151
+ }
1152
+ if (variesByDirection({
1153
+ node,
1154
+ printerOptions
1155
+ })) return true;
1156
+ const children = [];
1157
+ if ("properties" in node && node.properties) children.push(...node.properties.map((prop) => prop.schema));
1158
+ if ("items" in node && node.items) children.push(...node.items);
1159
+ if ("members" in node && node.members) children.push(...node.members);
1160
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
1161
+ return children.some((child) => containsDirectionalNode({
1162
+ node: child,
1163
+ printerOptions,
1164
+ seen
1165
+ }));
1166
+ }
1167
+ /**
1168
+ * Names of the `$ref` schemas the generator should route to their input (encode) variant.
1169
+ */
1170
+ function collectDirectionalRefNames({ node, printerOptions }) {
1171
+ return ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsDirectionalNode({
1172
+ node: n,
1173
+ printerOptions
1174
+ }) ? ast.resolveRefName(n) ?? void 0 : void 0 });
1175
+ }
1176
+ /**
1177
+ * Handlers that never recurse into children, so {@link variesByDirection} can call one directly
1178
+ * to probe both directions without building a printer.
1179
+ *
1180
+ * `date` is the built-in two-way conversion, decoding `string → Date` on responses and encoding
1181
+ * back on requests, keeping `date` and `date-time` precision apart. Only `representation: 'date'`
1182
+ * fields convert; ISO-string fields print `z.iso.date()` either way. A `printer.nodes.date`
1183
+ * override replaces the whole handler, direction branch included.
1184
+ */
1185
+ const scalarNodes = {
1186
+ any: () => "z.any()",
1187
+ unknown: () => "z.unknown()",
1188
+ void: () => "z.void()",
1189
+ never: () => "z.never()",
1190
+ boolean: () => "z.boolean()",
1191
+ null: () => "z.null()",
1192
+ string(node) {
1193
+ const base = shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()";
1194
+ const pattern = node.pattern ?? integerFormatPattern(node.format);
1195
+ return `${base}${lengthConstraints({
1196
+ ...node,
1197
+ pattern,
1198
+ regexType: this.options.regexType
1199
+ })}`;
1200
+ },
1201
+ number(node) {
1202
+ return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${numberConstraints(node)}`;
1203
+ },
1204
+ integer(node) {
1205
+ return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number().int()" : "z.int()"}${numberConstraints(node)}`;
1206
+ },
1207
+ bigint() {
1208
+ return "z.coerce.bigint()";
1209
+ },
1210
+ date(node) {
1211
+ if (node.representation !== "date") return "z.iso.date()";
1212
+ if (this.options.direction === "encode") return node.format === "date" ? "z.date().transform((value) => value.toISOString().slice(0, 10))" : "z.date().transform((value) => value.toISOString())";
1213
+ const decoded = node.format === "date" ? "z.iso.date().transform((value) => new Date(value))" : "z.iso.datetime().transform((value) => new Date(value))";
1214
+ return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : decoded;
1215
+ },
1216
+ datetime(node) {
1217
+ const offset = node.offset || this.options.dateType === "stringOffset";
1218
+ const local = node.local || this.options.dateType === "stringLocal";
1219
+ if (offset) return "z.iso.datetime({ offset: true })";
1220
+ if (local) return "z.iso.datetime({ local: true })";
1221
+ return "z.iso.datetime()";
1222
+ },
1223
+ time(node) {
1224
+ if (node.representation === "string") return "z.iso.time()";
1225
+ return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
1226
+ },
1227
+ uuid(node) {
1228
+ return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints({
1229
+ ...node,
1230
+ regexType: this.options.regexType
1231
+ })}`;
1232
+ },
1233
+ email(node) {
1234
+ return `z.email()${lengthConstraints({
1235
+ ...node,
1236
+ regexType: this.options.regexType
1237
+ })}`;
1238
+ },
1239
+ url(node) {
1240
+ return `z.url()${lengthConstraints({
1241
+ ...node,
1242
+ regexType: this.options.regexType
1243
+ })}`;
1244
+ },
1245
+ ipv4: () => "z.ipv4()",
1246
+ ipv6: () => "z.ipv6()",
1247
+ blob: () => "z.instanceof(File)",
1248
+ enum(node) {
1249
+ const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1250
+ if (node.namedEnumValues?.length) {
1251
+ const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`);
1252
+ if (literals.length === 1) return literals[0];
1253
+ return `z.union([${literals.join(", ")}])`;
1254
+ }
1255
+ return buildEnum(nonNullValues);
1256
+ }
1257
+ };
1258
+ /**
1156
1259
  * Zod v4 printer built with `definePrinter`.
1157
1260
  *
1158
1261
  * Converts a `SchemaNode` AST into a Zod v4 code string using the chainable API
@@ -1170,84 +1273,15 @@ const printerZod = ast.createPrinter((options) => {
1170
1273
  name: "zod",
1171
1274
  options,
1172
1275
  nodes: {
1173
- any: () => "z.any()",
1174
- unknown: () => "z.unknown()",
1175
- void: () => "z.void()",
1176
- never: () => "z.never()",
1177
- boolean: () => "z.boolean()",
1178
- null: () => "z.null()",
1179
- string(node) {
1180
- const base = shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()";
1181
- const pattern = node.pattern ?? integerFormatPattern(node.format);
1182
- return `${base}${lengthConstraints({
1183
- ...node,
1184
- pattern,
1185
- regexType: this.options.regexType
1186
- })}`;
1187
- },
1188
- number(node) {
1189
- return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${numberConstraints(node)}`;
1190
- },
1191
- integer(node) {
1192
- return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number().int()" : "z.int()"}${numberConstraints(node)}`;
1193
- },
1194
- bigint() {
1195
- return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
1196
- },
1197
- date(node) {
1198
- const codec = getCodec(node);
1199
- if (codec) {
1200
- if (this.options.direction === "input") return codec.encode(node);
1201
- return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : codec.decode(node);
1202
- }
1203
- return "z.iso.date()";
1204
- },
1205
- datetime(node) {
1206
- const offset = node.offset || this.options.dateType === "stringOffset";
1207
- const local = node.local || this.options.dateType === "stringLocal";
1208
- if (offset) return "z.iso.datetime({ offset: true })";
1209
- if (local) return "z.iso.datetime({ local: true })";
1210
- return "z.iso.datetime()";
1211
- },
1212
- time(node) {
1213
- if (node.representation === "string") return "z.iso.time()";
1214
- return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
1215
- },
1216
- uuid(node) {
1217
- return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints({
1218
- ...node,
1219
- regexType: this.options.regexType
1220
- })}`;
1221
- },
1222
- email(node) {
1223
- return `z.email()${lengthConstraints({
1224
- ...node,
1225
- regexType: this.options.regexType
1226
- })}`;
1227
- },
1228
- url(node) {
1229
- return `z.url()${lengthConstraints({
1230
- ...node,
1231
- regexType: this.options.regexType
1232
- })}`;
1233
- },
1234
- ipv4: () => "z.ipv4()",
1235
- ipv6: () => "z.ipv6()",
1236
- blob: () => "z.instanceof(File)",
1237
- enum(node) {
1238
- const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1239
- if (node.namedEnumValues?.length) {
1240
- const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`);
1241
- if (literals.length === 1) return literals[0];
1242
- return `z.union([${literals.join(", ")}])`;
1243
- }
1244
- return buildEnum(nonNullValues);
1245
- },
1276
+ ...scalarNodes,
1246
1277
  ref(node) {
1247
1278
  if (!node.name) return null;
1248
1279
  const refName = ast.resolveRefName(node);
1249
1280
  if (!refName) return null;
1250
- const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
1281
+ const useInputVariant = node.ref != null && this.options.direction === "encode" && containsDirectionalNode({
1282
+ node,
1283
+ printerOptions: this.options
1284
+ });
1251
1285
  const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.schema.inputName(refName) ?? refName : this.options.resolver?.name(refName) ?? refName : node.name;
1252
1286
  if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
1253
1287
  return resolvedName;
@@ -1431,7 +1465,7 @@ const printerZodMini = ast.createPrinter((options) => {
1431
1465
  return `z.int()${numberChecksMini(node)}`;
1432
1466
  },
1433
1467
  bigint(node) {
1434
- return `z.bigint()${numberChecksMini(node)}`;
1468
+ return `z.coerce.bigint()${numberChecksMini(node)}`;
1435
1469
  },
1436
1470
  date(node) {
1437
1471
  if (node.representation === "string") return "z.iso.date()";
@@ -1574,29 +1608,28 @@ const printerZodMini = ast.createPrinter((options) => {
1574
1608
  const zodPrinterCache = /* @__PURE__ */ new WeakMap();
1575
1609
  const zodMiniPrinterCache = /* @__PURE__ */ new WeakMap();
1576
1610
  /**
1577
- * Returns the cached `output`/`input` direction printers for a resolver, building them on
1578
- * first use. The `input` printer encodes `Date → string` for request bodies, and `output` decodes
1579
- * `string → Date` for responses. Schemas without `dateType: 'date'` fields print identically.
1611
+ * Cached printer per direction for a resolver, built on first use. Schemas holding nothing that
1612
+ * converts print the same either way.
1580
1613
  */
1581
1614
  function getStdPrinters(resolver, params) {
1582
1615
  const cached = zodPrinterCache.get(resolver);
1583
1616
  if (cached && cached.coercion === params.coercion && cached.guidType === params.guidType && cached.regexType === params.regexType && cached.dateType === params.dateType && cached.nodes === params.nodes) return {
1584
- output: cached.output,
1585
- input: cached.input
1617
+ decode: cached.decode,
1618
+ encode: cached.encode
1586
1619
  };
1587
- const output = printerZod({
1620
+ const decode = printerZod({
1588
1621
  ...params,
1589
1622
  resolver,
1590
- direction: "output"
1623
+ direction: "decode"
1591
1624
  });
1592
- const input = printerZod({
1625
+ const encode = printerZod({
1593
1626
  ...params,
1594
1627
  resolver,
1595
- direction: "input"
1628
+ direction: "encode"
1596
1629
  });
1597
1630
  zodPrinterCache.set(resolver, {
1598
- output,
1599
- input,
1631
+ decode,
1632
+ encode,
1600
1633
  coercion: params.coercion,
1601
1634
  guidType: params.guidType,
1602
1635
  regexType: params.regexType,
@@ -1604,8 +1637,8 @@ function getStdPrinters(resolver, params) {
1604
1637
  nodes: params.nodes
1605
1638
  });
1606
1639
  return {
1607
- output,
1608
- input
1640
+ decode,
1641
+ encode
1609
1642
  };
1610
1643
  }
1611
1644
  function getMiniPrinter(resolver, params) {
@@ -1639,15 +1672,30 @@ const zodGenerator = defineGenerator({
1639
1672
  if (!node.name) return;
1640
1673
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
1641
1674
  const cyclicSchemas = new Set(ctx.meta.circularNames);
1642
- const hasCodec = !mini && containsCodec(node);
1643
- const codecRefNames = new Set(hasCodec ? collectCodecRefNames(node) : []);
1675
+ const printerOptions = {
1676
+ coercion,
1677
+ guidType,
1678
+ regexType,
1679
+ dateType,
1680
+ resolver,
1681
+ cyclicSchemas,
1682
+ nodes: printer?.nodes
1683
+ };
1684
+ const hasDirectionalNode = !mini && containsDirectionalNode({
1685
+ node,
1686
+ printerOptions
1687
+ });
1688
+ const directionalRefNames = new Set(hasDirectionalNode ? collectDirectionalRefNames({
1689
+ node,
1690
+ printerOptions
1691
+ }) : []);
1644
1692
  const importEntries = resolver.imports({
1645
1693
  node,
1646
1694
  root,
1647
1695
  output,
1648
1696
  group: group ?? void 0
1649
1697
  });
1650
- const inputImportEntries = hasCodec ? [...codecRefNames].map((schemaName) => ({
1698
+ const inputImportEntries = hasDirectionalNode ? [...directionalRefNames].map((schemaName) => ({
1651
1699
  name: [resolver.schema.inputName(schemaName)],
1652
1700
  path: resolver.file({
1653
1701
  name: schemaName,
@@ -1688,7 +1736,7 @@ const zodGenerator = defineGenerator({
1688
1736
  regexType,
1689
1737
  cyclicSchemas,
1690
1738
  nodes: printer?.nodes
1691
- }) : stdPrinters.output;
1739
+ }) : stdPrinters.decode;
1692
1740
  return /* @__PURE__ */ jsxs(File, {
1693
1741
  baseName: meta.file.baseName,
1694
1742
  path: meta.file.path,
@@ -1731,10 +1779,10 @@ const zodGenerator = defineGenerator({
1731
1779
  inferTypeName,
1732
1780
  cyclic: cyclicSchemas.has(node.name)
1733
1781
  }),
1734
- hasCodec && stdPrinters && /* @__PURE__ */ jsx(Zod, {
1782
+ hasDirectionalNode && stdPrinters && /* @__PURE__ */ jsx(Zod, {
1735
1783
  name: resolver.schema.inputName(node.name),
1736
1784
  node,
1737
- printer: stdPrinters.input,
1785
+ printer: stdPrinters.encode,
1738
1786
  inferTypeName: inferred ? resolver.schema.inputTypeName(node.name) : null,
1739
1787
  cyclic: cyclicSchemas.has(node.name)
1740
1788
  })
@@ -1757,16 +1805,28 @@ const zodGenerator = defineGenerator({
1757
1805
  group: group ?? void 0
1758
1806
  }) };
1759
1807
  const cyclicSchemas = new Set(ctx.meta.circularNames);
1760
- function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
1808
+ const printerOptions = {
1809
+ coercion,
1810
+ guidType,
1811
+ regexType,
1812
+ dateType,
1813
+ resolver,
1814
+ cyclicSchemas,
1815
+ nodes: printer?.nodes
1816
+ };
1817
+ function renderSchemaEntry({ schema, name, keysToOmit, direction = "decode" }) {
1761
1818
  if (!schema) return null;
1762
1819
  const inferTypeName = inferred ? resolver.schema.type(name) : null;
1763
- const codecRefNames = direction === "input" && !mini ? new Set(collectCodecRefNames(schema)) : null;
1820
+ const directionalRefNames = direction === "encode" && !mini ? new Set(collectDirectionalRefNames({
1821
+ node: schema,
1822
+ printerOptions
1823
+ })) : null;
1764
1824
  const imports = resolver.imports({
1765
1825
  node: schema,
1766
1826
  root,
1767
1827
  output,
1768
1828
  group: group ?? void 0,
1769
- name: (schemaName) => codecRefNames?.has(schemaName) ? resolver.schema.inputName(schemaName) : resolver.name(schemaName)
1829
+ name: (schemaName) => directionalRefNames?.has(schemaName) ? resolver.schema.inputName(schemaName) : resolver.name(schemaName)
1770
1830
  });
1771
1831
  const schemaPrinter = mini ? keysToOmit?.length ? printerZodMini({
1772
1832
  guidType,
@@ -1855,7 +1915,7 @@ const zodGenerator = defineGenerator({
1855
1915
  const paramSchemas = node.parameters.map((param) => renderSchemaEntry({
1856
1916
  schema: param.schema,
1857
1917
  name: resolver.param.name(node, param),
1858
- direction: "input"
1918
+ direction: "encode"
1859
1919
  }));
1860
1920
  const responseSchemas = node.responses.map((res) => {
1861
1921
  const variants = (res.content ?? []).filter((entry) => entry.schema);
@@ -1893,13 +1953,13 @@ const zodGenerator = defineGenerator({
1893
1953
  },
1894
1954
  name: resolver.response.body(node),
1895
1955
  keysToOmit: entry.keysToOmit,
1896
- direction: "input"
1956
+ direction: "encode"
1897
1957
  });
1898
1958
  }
1899
1959
  return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), (schema) => ({
1900
1960
  ...schema,
1901
1961
  description: node.requestBody.description ?? schema.description
1902
- }), "input");
1962
+ }), "encode");
1903
1963
  })();
1904
1964
  const { path, query, header } = getOperationParameters(node);
1905
1965
  const paramGroupSchemas = inferred ? [
@@ -1918,12 +1978,12 @@ const zodGenerator = defineGenerator({
1918
1978
  ].filter(({ params }) => params.length > 0).map(({ kind, params }) => renderSchemaEntry({
1919
1979
  schema: buildGroupedParamsSchema({ params }),
1920
1980
  name: resolver.param[kind](node, params[0]),
1921
- direction: "input"
1981
+ direction: "encode"
1922
1982
  })) : [];
1923
1983
  const optionsSchema = inferred ? renderSchemaEntry({
1924
1984
  schema: buildOptionsSchema(node, resolver),
1925
1985
  name: resolver.name(`${node.operationId} Options`),
1926
- direction: "input"
1986
+ direction: "encode"
1927
1987
  }) : null;
1928
1988
  const responsesSchema = inferred ? renderSchemaEntry({
1929
1989
  schema: buildResponses(node, resolver),