@kubb/plugin-zod 5.7.2 → 5.7.3

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
@@ -1,8 +1,7 @@
1
1
  import { Diagnostics, Resolver, addGenerator, ast, containsCircularRef, createResolver, defineExtension, defineGenerator, extractRefName, setMacros, setResolver, syncSchemaRef } from "kubb/kit";
2
2
  import { Const, File, Function, Type, jsxRenderer } from "kubb/jsx";
3
3
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
4
- //#endregion
5
- //#region ../../internals/shared/src/operation.ts
4
+ // ../../internals/shared/src/operation.ts
6
5
  /**
7
6
  * Maps a content type to the PascalCase suffix used to name per-content-type variants
8
7
  * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
@@ -85,7 +84,10 @@ function getOperationParameters(node) {
85
84
  * shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
86
85
  * printer to emit `z.object(…)` rather than a record.
87
86
  */
88
- function buildOptionsSchema(node, resolver) {
87
+ function hasRequestContentTypeChoice(node) {
88
+ return (node.requestBody?.content ?? []).filter((entry) => entry.schema).length > 1;
89
+ }
90
+ function buildOptionsSchema(node, resolver, { pairContentTypes = false } = {}) {
89
91
  const { path, query, header } = getOperationParameters(node);
90
92
  const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
91
93
  const createNever = () => ast.factory.createSchema({
@@ -110,29 +112,57 @@ function buildOptionsSchema(node, resolver) {
110
112
  resolve: resolver.param.headers
111
113
  }
112
114
  ];
113
- return ast.factory.createSchema({
115
+ const createOptions = (body, extra = []) => ast.factory.createSchema({
114
116
  type: "object",
115
117
  primitive: "object",
116
118
  deprecated: node.deprecated,
117
- properties: [ast.factory.createProperty({
118
- name: "body",
119
- required: hasBody,
120
- schema: hasBody ? ast.factory.createSchema({
121
- type: "ref",
122
- name: resolver.response.body(node)
123
- }) : createNever()
124
- }), ...groups.map(({ name, params, resolve }) => {
125
- const required = params.some((param) => param.required);
126
- return ast.factory.createProperty({
127
- name,
128
- required,
129
- schema: params.length > 0 ? ast.factory.createSchema({
130
- type: "ref",
131
- name: resolve.call(resolver.param, node, params[0]),
132
- optional: !required
133
- }) : createNever()
134
- });
135
- })]
119
+ properties: [
120
+ ast.factory.createProperty({
121
+ name: "body",
122
+ required: hasBody,
123
+ schema: body
124
+ }),
125
+ ...groups.map(({ name, params, resolve }) => {
126
+ const required = params.some((param) => param.required);
127
+ return ast.factory.createProperty({
128
+ name,
129
+ required,
130
+ schema: params.length > 0 ? ast.factory.createSchema({
131
+ type: "ref",
132
+ name: resolve.call(resolver.param, node, params[0]),
133
+ optional: !required
134
+ }) : createNever()
135
+ });
136
+ }),
137
+ ...extra
138
+ ]
139
+ });
140
+ const variants = pairContentTypes && hasRequestContentTypeChoice(node) ? resolveContentTypeVariants(node.requestBody?.content ?? [], resolver.response.body(node)) : [];
141
+ if (variants.length < 2) return createOptions(hasBody ? ast.factory.createSchema({
142
+ type: "ref",
143
+ name: resolver.response.body(node)
144
+ }) : createNever());
145
+ return ast.factory.createSchema({
146
+ type: "union",
147
+ members: variants.map((variant, index) => createOptions(ast.factory.createSchema({
148
+ type: "ref",
149
+ name: variant.name
150
+ }), [ast.factory.createProperty({
151
+ name: "contentType",
152
+ required: index > 0,
153
+ schema: ast.factory.createSchema({
154
+ type: "object",
155
+ primitive: "object",
156
+ properties: [ast.factory.createProperty({
157
+ name: "request",
158
+ required: true,
159
+ schema: ast.factory.createSchema({
160
+ type: "enum",
161
+ enumValues: [variant.contentType]
162
+ })
163
+ })]
164
+ })
165
+ })]))
136
166
  });
137
167
  }
138
168
  /**
@@ -202,8 +232,7 @@ function isSuccessStatusCode(statusCode) {
202
232
  function getSuccessResponses(responses) {
203
233
  return responses.filter((response) => isSuccessStatusCode(response.statusCode));
204
234
  }
205
- //#endregion
206
- //#region ../../internals/shared/src/adapter.ts
235
+ // ../../internals/shared/src/adapter.ts
207
236
  /**
208
237
  * Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
209
238
  * so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
@@ -226,8 +255,7 @@ function getOasAdapter(adapter) {
226
255
  });
227
256
  return adapter;
228
257
  }
229
- //#endregion
230
- //#region ../../internals/utils/src/casing.ts
258
+ // ../../internals/utils/src/casing.ts
231
259
  /**
232
260
  * Shared implementation for camelCase and PascalCase conversion.
233
261
  * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
@@ -276,8 +304,7 @@ function camelCase(text, { prefix = "", suffix = "" } = {}) {
276
304
  function pascalCase(text, { prefix = "", suffix = "" } = {}) {
277
305
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
278
306
  }
279
- //#endregion
280
- //#region ../../internals/utils/src/reserved.ts
307
+ // ../../internals/utils/src/reserved.ts
281
308
  /**
282
309
  * JavaScript and Java reserved words.
283
310
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
@@ -285,6 +312,7 @@ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
285
312
  const reservedWords = /* @__PURE__ */ new Set([
286
313
  "abstract",
287
314
  "arguments",
315
+ "await",
288
316
  "boolean",
289
317
  "break",
290
318
  "byte",
@@ -415,8 +443,7 @@ function ensureValidVarName(name) {
415
443
  function isIdentifier(name) {
416
444
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
417
445
  }
418
- //#endregion
419
- //#region ../../internals/utils/src/strings.ts
446
+ // ../../internals/utils/src/strings.ts
420
447
  /**
421
448
  * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
422
449
  * any backslash or single quote in the content.
@@ -465,6 +492,34 @@ function stringify(value) {
465
492
  if (value === void 0 || value === null) return "''";
466
493
  return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
467
494
  }
495
+ const NEEDS_UNICODE = /(?<!\\)(?:\\\\)*\\[pPu]\{|[\u{10000}-\u{10FFFF}]/u;
496
+ const KEPT_ESCAPE = /[A-Za-z0-9^$\\.*+?()[\]{}|/]/;
497
+ /**
498
+ * Drops the backslash from escapes that the `u` flag rejects: an escaped punctuation character that is
499
+ * not a syntax character, and `\-` outside a class. Both match the same character without the backslash.
500
+ */
501
+ function dropIdentityEscapes(pattern) {
502
+ let inClass = false;
503
+ return pattern.replace(/\\(.)|[[\]]/gs, (match, escaped) => {
504
+ if (escaped === void 0) {
505
+ inClass = match === "[";
506
+ return match;
507
+ }
508
+ return KEPT_ESCAPE.test(escaped) || escaped === "-" && inClass ? match : escaped;
509
+ });
510
+ }
511
+ /**
512
+ * Builds the `RegExp` for a pattern, adding the `u` flag when it uses `\p{...}`, `\P{...}` or `\u{...}`, or
513
+ * holds a character outside the BMP. A pattern that is not valid under `u` keeps the flags it came with.
514
+ */
515
+ function toRegExp(pattern, flags) {
516
+ if (flags?.includes("u") || !NEEDS_UNICODE.test(pattern)) return new RegExp(pattern, flags);
517
+ try {
518
+ return new RegExp(dropIdentityEscapes(pattern), `${flags ?? ""}u`);
519
+ } catch {
520
+ return new RegExp(pattern, flags);
521
+ }
522
+ }
468
523
  /**
469
524
  * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
470
525
  * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
@@ -481,13 +536,11 @@ function toRegExpString(text, func = "RegExp") {
481
536
  const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
482
537
  const replacementTarget = match?.[1] ?? "";
483
538
  const matchedFlags = match?.[2];
484
- const cleaned = raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, "");
485
- const { source, flags } = new RegExp(cleaned, matchedFlags);
539
+ const { source, flags } = toRegExp(raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, ""), matchedFlags);
486
540
  if (func === null) return `/${source}/${flags}`;
487
541
  return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
488
542
  }
489
- //#endregion
490
- //#region ../../internals/utils/src/codegen.ts
543
+ // ../../internals/utils/src/codegen.ts
491
544
  const INDENT = " ";
492
545
  /**
493
546
  * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
@@ -556,8 +609,7 @@ function buildList(items, brackets = ["[", "]"]) {
556
609
  function lazyGetter({ name, body }) {
557
610
  return `get ${objectKey(name)}() { return ${body} }`;
558
611
  }
559
- //#endregion
560
- //#region ../../internals/utils/src/fs.ts
612
+ // ../../internals/utils/src/fs.ts
561
613
  /**
562
614
  * Builds a nested file path from a dotted name. Splits on dots that precede a letter
563
615
  * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
@@ -581,17 +633,24 @@ function toFilePath(name, caseLast = camelCase) {
581
633
  const parts = name.split(/\.(?=[a-zA-Z])/);
582
634
  return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
583
635
  }
584
- //#endregion
585
- //#region ../../internals/shared/src/resolver.ts
636
+ // ../../internals/shared/src/resolver.ts
586
637
  /**
587
- * Resolves a single operation parameter name with the
588
- * `<operationId> <in> <name>` template.
638
+ * Builds the allocation key for a declaration an operation owns. `scope` names which declaration
639
+ * it is, so two of them never share an allocation even when a custom resolver gives both the same
640
+ * name. Leave `scope` empty for the operation's own declaration.
589
641
  *
590
642
  * @example
591
- * `operationParamName.call(resolver, node, param) // → 'DeletePetPathPetId'`
643
+ * ```ts
644
+ * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
645
+ * ```
592
646
  */
593
- function operationParamName(node, param) {
594
- return this.name(`${node.operationId} ${param.in} ${param.name}`);
647
+ function operationKey(node, ...scope) {
648
+ return [
649
+ "operation",
650
+ node.method,
651
+ node.path,
652
+ ...scope.map(String)
653
+ ];
595
654
  }
596
655
  /**
597
656
  * Builds the shared `param` namespace. Spread the result into `createResolver`
@@ -604,15 +663,33 @@ function operationParamName(node, param) {
604
663
  */
605
664
  function createOperationParamResolver() {
606
665
  return {
607
- name: operationParamName,
666
+ name(node, param) {
667
+ const name = this.name(`${node.operationId} ${param.in} ${param.name}`);
668
+ return this.resolveName({
669
+ key: operationKey(node, "param", "name", param.in, param.name),
670
+ name
671
+ });
672
+ },
608
673
  path(node) {
609
- return this.name(`${node.operationId} Path`);
674
+ const name = this.name(`${node.operationId} Path`);
675
+ return this.resolveName({
676
+ key: operationKey(node, "param", "path"),
677
+ name
678
+ });
610
679
  },
611
680
  query(node) {
612
- return this.name(`${node.operationId} Query`);
681
+ const name = this.name(`${node.operationId} Query`);
682
+ return this.resolveName({
683
+ key: operationKey(node, "param", "query"),
684
+ name
685
+ });
613
686
  },
614
687
  headers(node) {
615
- return this.name(`${node.operationId} Headers`);
688
+ const name = this.name(`${node.operationId} Headers`);
689
+ return this.resolveName({
690
+ key: operationKey(node, "param", "headers"),
691
+ name
692
+ });
616
693
  }
617
694
  };
618
695
  }
@@ -629,16 +706,32 @@ function createOperationParamResolver() {
629
706
  function createOperationResponseResolver() {
630
707
  return {
631
708
  status(node, statusCode) {
632
- return this.name(`${node.operationId} Status ${statusCode}`);
709
+ const name = this.name(`${node.operationId} Status ${statusCode}`);
710
+ return this.resolveName({
711
+ key: operationKey(node, "response", "status", statusCode),
712
+ name
713
+ });
633
714
  },
634
715
  body(node) {
635
- return this.name(`${node.operationId} Body`);
716
+ const name = this.name(`${node.operationId} Body`);
717
+ return this.resolveName({
718
+ key: operationKey(node, "response", "body"),
719
+ name
720
+ });
636
721
  },
637
722
  responses(node) {
638
- return this.name(`${node.operationId} Responses`);
723
+ const name = this.name(`${node.operationId} Responses`);
724
+ return this.resolveName({
725
+ key: operationKey(node, "response", "responses"),
726
+ name
727
+ });
639
728
  },
640
729
  response(node) {
641
- return this.name(`${node.operationId} Response`);
730
+ const name = this.name(`${node.operationId} Response`);
731
+ return this.resolveName({
732
+ key: operationKey(node, "response", "response"),
733
+ name
734
+ });
642
735
  }
643
736
  };
644
737
  }
@@ -656,8 +749,7 @@ function createCasedFile(caseLast) {
656
749
  return `${toFilePath(name, caseLast)}${extname}`;
657
750
  } };
658
751
  }
659
- //#endregion
660
- //#region ../../internals/shared/src/group.ts
752
+ // ../../internals/shared/src/group.ts
661
753
  /**
662
754
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
663
755
  * shared default naming so every plugin groups output consistently:
@@ -687,8 +779,7 @@ function createGroupConfig(group) {
687
779
  name: group.name ? group.name : defaultName
688
780
  };
689
781
  }
690
- //#endregion
691
- //#region ../../internals/shared/src/schemaTraversal.ts
782
+ // ../../internals/shared/src/schemaTraversal.ts
692
783
  /**
693
784
  * Maps each property of an object schema to its transformed output. Pairs every result with the
694
785
  * original property so the printer keeps full control over modifiers, getters, and key syntax.
@@ -726,8 +817,7 @@ function mapSchemaItems(node, transform) {
726
817
  output: transform(schema)
727
818
  }));
728
819
  }
729
- //#endregion
730
- //#region src/components/Zod.tsx
820
+ // src/components/Zod.tsx
731
821
  function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuards, isName, assertName, mini, cyclic, compile }) {
732
822
  const output = printer.print(node);
733
823
  const printerImports = printer.drainImports();
@@ -799,8 +889,7 @@ function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuar
799
889
  })
800
890
  ] });
801
891
  }
802
- //#endregion
803
- //#region src/utils.ts
892
+ // src/utils.ts
804
893
  /**
805
894
  * Returns `true` when the given coercion option enables coercion for the specified type.
806
895
  */
@@ -829,11 +918,12 @@ function isClosedKeySchema(node) {
829
918
  }
830
919
  /**
831
920
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
832
- * argument. A catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
921
+ * argument. Property counts, a catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
833
922
  * on `.and(…)`.
834
923
  */
835
924
  function isPlainInlineObject(node) {
836
925
  if (node.type !== "object") return false;
926
+ if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
837
927
  if (node.nullable || node.optional || node.nullish) return false;
838
928
  const hasAdditionalProperties = node.additionalProperties !== void 0;
839
929
  const hasPatternProperties = Boolean(node.patternProperties);
@@ -844,11 +934,12 @@ function isPlainInlineObject(node) {
844
934
  * `z.discriminatedUnion` option. Covers object nodes, `$ref`s that resolve to (or are still
845
935
  * unresolved) objects, single-member unions of an object, and object-composable `allOf`.
846
936
  *
847
- * `cyclicSchemas` bounds the recursion: a ref into a cycle renders as `z.lazy(…)`, not an object.
937
+ * Property counts render as a pipe. A ref into `cyclicSchemas` renders as `z.lazy(…)`.
848
938
  */
849
939
  function isObjectSchemaNode(node, cyclicSchemas) {
850
940
  if (node.nullable || node.optional || node.nullish) return false;
851
941
  if (node.type === "object") {
942
+ if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
852
943
  const hasProperties = Boolean(node.properties?.length);
853
944
  const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
854
945
  const hasPatternProperties = Boolean(node.patternProperties);
@@ -876,6 +967,49 @@ function isObjectComposableIntersection(node, cyclicSchemas) {
876
967
  const [first, ...rest] = node.members ?? [];
877
968
  return !!first && isObjectSchemaNode(first, cyclicSchemas) && rest.every(isPlainInlineObject);
878
969
  }
970
+ function preserveRequiredProperties(members) {
971
+ const hasRequiredProperty = ({ node, propertyName, seen = /* @__PURE__ */ new Set() }) => {
972
+ if (seen.has(node)) return false;
973
+ seen.add(node);
974
+ const resolved = syncSchemaRef(node);
975
+ const composition = ast.narrowSchema(resolved, ast.schemaTypes.intersection) ?? ast.narrowSchema(resolved, ast.schemaTypes.union);
976
+ if (composition) {
977
+ if (composition.type === ast.schemaTypes.union && composition.members?.length !== 1) return false;
978
+ return composition.members?.some((child) => hasRequiredProperty({
979
+ node: child,
980
+ propertyName,
981
+ seen
982
+ })) ?? false;
983
+ }
984
+ return ast.resolveSchemaProperties({
985
+ node: resolved,
986
+ propertyName
987
+ }).some((property) => property.required !== false && !property.schema.optional && !property.schema.nullish);
988
+ };
989
+ return members.map((member, index) => {
990
+ const object = ast.narrowSchema(member, ast.schemaTypes.object);
991
+ if (!object || index === 0) return member;
992
+ return {
993
+ ...object,
994
+ properties: object.properties.map((property) => {
995
+ if (!members.slice(0, index).some((base) => hasRequiredProperty({
996
+ node: base,
997
+ propertyName: property.name
998
+ }))) return property;
999
+ return {
1000
+ ...property,
1001
+ required: true,
1002
+ schema: {
1003
+ ...property.schema,
1004
+ optional: false,
1005
+ nullish: false,
1006
+ nullable: property.schema.nullable || property.schema.nullish
1007
+ }
1008
+ };
1009
+ })
1010
+ };
1011
+ });
1012
+ }
879
1013
  /**
880
1014
  * Format a default value as a code-level literal.
881
1015
  * Arrays and objects keep their contents as a JSON literal, primitives become their string
@@ -922,11 +1056,20 @@ function defaultLiteral(node, value) {
922
1056
  */
923
1057
  function buildEnum(values) {
924
1058
  if (values.every((v) => typeof v === "string")) return `z.enum([${values.map((v) => formatDefault(v)).join(", ")}])`;
925
- const literals = values.map((v) => `z.literal(${formatDefault(v)})`);
1059
+ const literals = values.map((value) => buildLiteral(value));
926
1060
  if (literals.length === 1) return literals[0];
927
1061
  return `z.union([${literals.join(", ")}])`;
928
1062
  }
929
1063
  /**
1064
+ * Preserves the structure of JSON const values in both Zod APIs.
1065
+ */
1066
+ function buildLiteral(value) {
1067
+ if (value === null) return "z.null()";
1068
+ if (Array.isArray(value)) return `z.tuple([${value.map((item) => buildLiteral(item)).join(", ")}])`;
1069
+ if (typeof value === "object") return `z.strictObject({${Object.entries(value).map(([name, item]) => `${JSON.stringify(name)}: ${buildLiteral(item)}`).join(", ")}})`;
1070
+ return `z.literal(${formatDefault(value)})`;
1071
+ }
1072
+ /**
930
1073
  * Digit pattern for a `type: 'string'` schema that carries an integer `format`, the way ProtoJSON
931
1074
  * encodes 64-bit integers. Returns `undefined` for every other format, and a `pattern` from the
932
1075
  * spec takes precedence over this fallback.
@@ -1009,6 +1152,17 @@ function lengthChecksMini({ min, max, pattern, regexType }) {
1009
1152
  return checks.length ? `.check(${checks.join(", ")})` : "";
1010
1153
  }
1011
1154
  /**
1155
+ * Checks property counts before object parsing strips unknown keys or inserts defaults.
1156
+ */
1157
+ function objectConstraints({ value, node }) {
1158
+ if (node.type !== "object") return value;
1159
+ const checks = [];
1160
+ const skip = "typeof value !== \"object\" || value === null || Array.isArray(value)";
1161
+ if (node.minProperties !== void 0) checks.push(`z.refine(value => ${skip} || Object.keys(value).length >= ${node.minProperties}, { message: "Object must have at least ${node.minProperties} properties" })`);
1162
+ if (node.maxProperties !== void 0) checks.push(`z.refine(value => ${skip} || Object.keys(value).length <= ${node.maxProperties}, { message: "Object must have at most ${node.maxProperties} properties" })`);
1163
+ return checks.length ? `z.pipe(z.unknown().check(${checks.join(", ")}), ${value})` : value;
1164
+ }
1165
+ /**
1012
1166
  * Apply nullable / optional / nullish modifiers, an optional `.describe()` call, and an
1013
1167
  * optional `.meta({ examples })` call to a schema value string using the chainable Zod v4 API.
1014
1168
  */
@@ -1098,7 +1252,7 @@ function getMemberConstraint({ member, regexType, lengths, numbers }) {
1098
1252
  /**
1099
1253
  * Builds the `{ key: value, … }` shape shared by the `z.object(...)` and `.extend(...)` renderings.
1100
1254
  */
1101
- function buildObjectShape(ctx, node, modify) {
1255
+ function buildObjectShape(ctx, node, renderProperty) {
1102
1256
  const objectNode = ast.narrowSchema(node, "object");
1103
1257
  if (!objectNode) return "{}";
1104
1258
  const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
@@ -1109,18 +1263,16 @@ function buildObjectShape(ctx, node, modify) {
1109
1263
  const baseOutput = ctx.transform(schema) ?? ctx.transform(ast.factory.createSchema({ type: "unknown" }));
1110
1264
  if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
1111
1265
  return baseOutput;
1112
- }).map(({ name: propName, property, output: baseOutput }) => {
1266
+ }).map(({ name, property, output: value }) => {
1113
1267
  const { schema } = property;
1114
- const value = modify({
1115
- value: baseOutput,
1268
+ return renderProperty({
1269
+ name,
1270
+ value,
1116
1271
  schema,
1117
1272
  required: property.required,
1118
- meta: syncSchemaRef(schema)
1273
+ meta: syncSchemaRef(schema),
1274
+ cyclic: isCyclic(schema)
1119
1275
  });
1120
- return isCyclic(schema) ? lazyGetter({
1121
- name: propName,
1122
- body: value
1123
- }) : `${objectKey(propName)}: ${value}`;
1124
1276
  }));
1125
1277
  }
1126
1278
  /**
@@ -1135,22 +1287,28 @@ function printWithOmit(ctx, node, modify, mini = false) {
1135
1287
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
1136
1288
  const unwrap = omitUnwrapChain(node, mini ? "._zod.def.innerType" : ".unwrap()");
1137
1289
  const mask = `{ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }`;
1138
- const omit = (target) => mini ? `z.omit(${target}${unwrap}, ${mask})` : `${target}${unwrap}.omit(${mask})`;
1290
+ const omit = (target) => {
1291
+ const inner = `${target}${unwrap}`;
1292
+ const hasObjectConstraints = meta.type === "object" && (meta.minProperties !== void 0 || meta.maxProperties !== void 0);
1293
+ const pipe = mini ? `${inner}._zod.def` : inner;
1294
+ const object = hasObjectConstraints ? `${pipe}.out` : inner;
1295
+ const omitted = mini ? `z.omit(${object}, ${mask})` : `${object}.omit(${mask})`;
1296
+ return hasObjectConstraints ? `z.pipe(${pipe}.in, ${omitted})` : omitted;
1297
+ };
1139
1298
  const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
1140
1299
  if (lazyMatch) return `z.lazy(() => ${omit(lazyMatch[1])})`;
1141
1300
  return omit(transformed);
1142
1301
  })(), meta);
1143
1302
  }
1144
- //#endregion
1145
- //#region src/printers/printerZod.ts
1303
+ // src/printers/printerZod.ts
1146
1304
  function strictOneOfMember(member, node, cyclicSchemas) {
1147
- if (node.type === "ref") {
1305
+ if (node.type === ast.schemaTypes.ref) {
1148
1306
  if (member.startsWith("z.lazy(")) return member;
1149
1307
  const refName = ast.resolveRefName(node);
1150
1308
  if (refName && cyclicSchemas?.has(refName)) return member;
1151
1309
  const schema = syncSchemaRef(node);
1152
1310
  if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
1153
- if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
1311
+ if (schema.type === ast.schemaTypes.object && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
1154
1312
  }
1155
1313
  return member;
1156
1314
  }
@@ -1158,28 +1316,70 @@ function strictOneOfMember(member, node, cyclicSchemas) {
1158
1316
  * Builds the `{ key: value, … }` shape for an object node with the chainable modifiers.
1159
1317
  */
1160
1318
  function buildZodObjectShape(ctx, node) {
1161
- return buildObjectShape(ctx, node, ({ value, schema, required, meta }) => applyModifiers({
1162
- value,
1163
- schema,
1164
- nullable: meta.nullable,
1165
- optional: schema.optional || required === false,
1166
- nullish: schema.nullish,
1167
- defaultValue: meta.default,
1168
- description: schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description,
1169
- examples: meta.examples
1170
- }));
1319
+ return buildObjectShape(ctx, node, ({ name, value, schema, required, meta, cyclic }) => {
1320
+ const body = applyModifiers({
1321
+ value,
1322
+ schema,
1323
+ nullable: meta.nullable,
1324
+ optional: schema.optional || required === false,
1325
+ nullish: schema.nullish,
1326
+ defaultValue: meta.default,
1327
+ description: schema.type !== ast.schemaTypes.ref && meta.type === ast.schemaTypes.ref ? void 0 : meta.description,
1328
+ examples: meta.examples
1329
+ });
1330
+ if (!cyclic) return `${objectKey(name)}: ${body}`;
1331
+ const returnType = recursiveGetterType({
1332
+ ctx,
1333
+ schema,
1334
+ required
1335
+ });
1336
+ return returnType ? `get ${objectKey(name)}(): ${returnType} { return ${body} }` : lazyGetter({
1337
+ name,
1338
+ body
1339
+ });
1340
+ });
1341
+ }
1342
+ /**
1343
+ * Breaks getter inference cycles without widening recursive refs and arrays to `z.ZodType`.
1344
+ */
1345
+ function recursiveGetterType({ ctx, schema, required }) {
1346
+ function baseType(schema) {
1347
+ if (ctx.options.nodes?.[schema.type]) return void 0;
1348
+ if (schema.type === ast.schemaTypes.ref) {
1349
+ const saved = ctx.options.cyclicSchemas;
1350
+ ctx.options.cyclicSchemas = void 0;
1351
+ const name = ctx.transform(schema);
1352
+ ctx.options.cyclicSchemas = saved;
1353
+ return name ? `typeof ${name}` : void 0;
1354
+ }
1355
+ if (schema.type === ast.schemaTypes.array && schema.items?.length === 1) {
1356
+ const item = baseType(schema.items[0]);
1357
+ return item ? `z.ZodArray<${item}>` : void 0;
1358
+ }
1359
+ }
1360
+ const base = baseType(schema);
1361
+ if (!base) return void 0;
1362
+ const meta = syncSchemaRef(schema);
1363
+ const optional = schema.optional || required === false;
1364
+ const wrapped = (() => {
1365
+ if (schema.nullish || meta.nullable && optional) return `z.ZodOptional<z.ZodNullable<${base}>>`;
1366
+ if (optional) return `z.ZodOptional<${base}>`;
1367
+ if (meta.nullable) return `z.ZodNullable<${base}>`;
1368
+ return base;
1369
+ })();
1370
+ return meta.default !== void 0 && defaultLiteral(schema, meta.default) !== null ? `z.ZodDefault<${wrapped}>` : wrapped;
1171
1371
  }
1172
1372
  /**
1173
1373
  * Types the direction probe skips. They delegate to their children rather than reading
1174
1374
  * `direction` themselves, so {@link containsDirectionalNode} walks into the children instead.
1175
1375
  */
1176
1376
  const CONTAINER_TYPES = /* @__PURE__ */ new Set([
1177
- "object",
1178
- "array",
1179
- "tuple",
1180
- "union",
1181
- "intersection",
1182
- "ref"
1377
+ ast.schemaTypes.object,
1378
+ ast.schemaTypes.array,
1379
+ ast.schemaTypes.tuple,
1380
+ ast.schemaTypes.union,
1381
+ ast.schemaTypes.intersection,
1382
+ ast.schemaTypes.ref
1183
1383
  ]);
1184
1384
  /**
1185
1385
  * Runs the node's effective handler (a `printer.nodes` override, else the built-in) once per
@@ -1211,7 +1411,7 @@ function variesByDirection({ node, printerOptions }) {
1211
1411
  */
1212
1412
  function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */ new Set() }) {
1213
1413
  if (!node) return false;
1214
- if (node.type === "ref") {
1414
+ if (node.type === ast.schemaTypes.ref) {
1215
1415
  if (!node.ref) return false;
1216
1416
  const refName = extractRefName(node.ref);
1217
1417
  if (refName) {
@@ -1219,7 +1419,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1219
1419
  seen.add(refName);
1220
1420
  }
1221
1421
  const resolved = syncSchemaRef(node);
1222
- if (resolved.type === "ref") return false;
1422
+ if (resolved.type === ast.schemaTypes.ref) return false;
1223
1423
  return containsDirectionalNode({
1224
1424
  node: resolved,
1225
1425
  printerOptions,
@@ -1235,7 +1435,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1235
1435
  if ("items" in node && node.items) children.push(...node.items);
1236
1436
  if ("members" in node && node.members) children.push(...node.members);
1237
1437
  if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
1238
- const propertyNames = node.type === "object" ? node.propertyNames : void 0;
1438
+ const propertyNames = node.type === ast.schemaTypes.object ? node.propertyNames : void 0;
1239
1439
  if (propertyNames) children.push(propertyNames);
1240
1440
  return children.some((child) => containsDirectionalNode({
1241
1441
  node: child,
@@ -1247,7 +1447,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1247
1447
  * Names of the `$ref` schemas the generator should route to their input (encode) variant.
1248
1448
  */
1249
1449
  function collectDirectionalRefNames({ node, printerOptions }) {
1250
- return ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsDirectionalNode({
1450
+ return ast.collectSync(node, { schema: (n) => n.type === ast.schemaTypes.ref && n.ref && containsDirectionalNode({
1251
1451
  node: n,
1252
1452
  printerOptions
1253
1453
  }) ? ast.resolveRefName(n) ?? void 0 : void 0 });
@@ -1339,7 +1539,7 @@ const scalarNodes = {
1339
1539
  enum(node) {
1340
1540
  const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1341
1541
  if (node.namedEnumValues?.length) {
1342
- const literals = nonNullValues.map((v) => `z.literal(${formatDefault(v)})`);
1542
+ const literals = nonNullValues.map((v) => buildLiteral(v));
1343
1543
  if (literals.length === 1) return literals[0];
1344
1544
  return `z.union([${literals.join(", ")}])`;
1345
1545
  }
@@ -1360,6 +1560,7 @@ const scalarNodes = {
1360
1560
  */
1361
1561
  const printerZod = ast.createPrinter((options) => {
1362
1562
  const cyclicSchemaNames = options.cyclicSchemas;
1563
+ const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
1363
1564
  return {
1364
1565
  name: "zod",
1365
1566
  options,
@@ -1385,37 +1586,40 @@ const printerZod = ast.createPrinter((options) => {
1385
1586
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1386
1587
  const isLoose = node.additionalProperties === true;
1387
1588
  const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodObjectShape(this, node)})`;
1388
- return (() => {
1389
- const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1390
- if (node.additionalProperties && node.additionalProperties !== true) {
1391
- const catchallType = this.transform(node.additionalProperties);
1392
- if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1393
- return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1394
- }
1395
- if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1396
- const unknownType = this.transform(ast.factory.createSchema({ type: "unknown" }));
1397
- return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1398
- }
1399
- if (node.additionalProperties === true) return objectBase;
1400
- if (isStrict) return objectBase;
1401
- if (patterns.length > 0) {
1402
- const values = patterns.map(([, valueSchema]) => {
1403
- const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
1404
- return valueSchema.nullable ? `${valueType}.nullable()` : valueType;
1405
- });
1406
- const distinct = [...new Set(values)];
1407
- const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1408
- if (entries.length > 0) return `${objectBase}.catchall(${value})`;
1409
- return `z.record(${patternKeySchema({
1410
- patterns: patterns.map(([pattern]) => pattern),
1411
- regexType: this.options.regexType
1412
- })}, ${value})`;
1413
- }
1414
- return objectBase;
1415
- })();
1589
+ return objectConstraints({
1590
+ value: (() => {
1591
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1592
+ if (node.additionalProperties && node.additionalProperties !== true) {
1593
+ const catchallType = this.transform(node.additionalProperties);
1594
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1595
+ return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1596
+ }
1597
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1598
+ const unknownType = this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1599
+ return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1600
+ }
1601
+ if (node.additionalProperties === true) return objectBase;
1602
+ if (isStrict) return objectBase;
1603
+ if (patterns.length > 0) {
1604
+ const values = patterns.map(([, valueSchema]) => {
1605
+ const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1606
+ return valueSchema.nullable ? `${valueType}.nullable()` : valueType;
1607
+ });
1608
+ const distinct = [...new Set(values)];
1609
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1610
+ if (entries.length > 0) return `${objectBase}.catchall(${value})`;
1611
+ return `z.record(${patternKeySchema({
1612
+ patterns: patterns.map(([pattern]) => pattern),
1613
+ regexType: this.options.regexType
1614
+ })}, ${value})`;
1615
+ }
1616
+ return objectBase;
1617
+ })(),
1618
+ node
1619
+ });
1416
1620
  },
1417
1621
  array(node) {
1418
- const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthConstraints({
1622
+ const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }))})${lengthConstraints({
1419
1623
  ...node,
1420
1624
  regexType: this.options.regexType
1421
1625
  })}`;
@@ -1428,7 +1632,7 @@ const printerZod = ast.createPrinter((options) => {
1428
1632
  const nodeMembers = node.members ?? [];
1429
1633
  const isDiscriminated = !!node.discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1430
1634
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1431
- const isStrict = node.strategy === "one" && !isXor;
1635
+ const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1432
1636
  const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ schema, output }) => output && isStrict ? strictOneOfMember(output, schema, cyclicSchemaNames) : output).filter(Boolean);
1433
1637
  if (members.length === 0) return "";
1434
1638
  if (members.length === 1) return members[0];
@@ -1439,11 +1643,21 @@ const printerZod = ast.createPrinter((options) => {
1439
1643
  intersection(node) {
1440
1644
  const members = node.members ?? [];
1441
1645
  if (members.length === 0) return "";
1646
+ const hasSiblingProperties = members.some((member) => member.type === ast.schemaTypes.object && member.properties.length > 0);
1647
+ const transform = (member) => {
1648
+ if (!hasSiblingProperties) return this.transform(member);
1649
+ unionsWithSiblings.add(member);
1650
+ try {
1651
+ return this.transform(member);
1652
+ } finally {
1653
+ unionsWithSiblings.delete(member);
1654
+ }
1655
+ };
1442
1656
  const [first, ...rest] = members;
1443
1657
  if (!first) return "";
1444
- const firstBase = this.transform(first);
1658
+ const firstBase = transform(first);
1445
1659
  if (!firstBase) return "";
1446
- if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `${acc}.extend(${buildZodObjectShape(this, member)})`, firstBase);
1660
+ if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return preserveRequiredProperties(members).slice(1).reduce((acc, member) => `${acc}.extend(${buildZodObjectShape(this, member)})`, firstBase);
1447
1661
  return rest.reduce((acc, member) => {
1448
1662
  const constraint = getMemberConstraint({
1449
1663
  member,
@@ -1452,7 +1666,7 @@ const printerZod = ast.createPrinter((options) => {
1452
1666
  numbers: numberConstraints
1453
1667
  });
1454
1668
  if (constraint) return acc + constraint;
1455
- const transformed = this.transform(member);
1669
+ const transformed = transform(member);
1456
1670
  return transformed ? `${acc}.and(${transformed})` : acc;
1457
1671
  }, firstBase);
1458
1672
  }
@@ -1472,20 +1686,25 @@ const printerZod = ast.createPrinter((options) => {
1472
1686
  }
1473
1687
  };
1474
1688
  });
1475
- //#endregion
1476
- //#region src/printers/printerZodMini.ts
1689
+ // src/printers/printerZodMini.ts
1477
1690
  /**
1478
1691
  * Builds the `{ key: value, … }` shape for an object node with the functional `zod/mini` modifiers.
1479
1692
  */
1480
1693
  function buildZodMiniObjectShape(ctx, node) {
1481
- return buildObjectShape(ctx, node, ({ value, schema, required, meta }) => applyMiniModifiers({
1482
- value,
1483
- schema,
1484
- nullable: meta.nullable,
1485
- optional: schema.optional || required === false,
1486
- nullish: schema.nullish,
1487
- defaultValue: meta.default
1488
- }));
1694
+ return buildObjectShape(ctx, node, ({ name, value, schema, required, meta, cyclic }) => {
1695
+ const body = applyMiniModifiers({
1696
+ value,
1697
+ schema,
1698
+ nullable: meta.nullable,
1699
+ optional: schema.optional || required === false,
1700
+ nullish: schema.nullish,
1701
+ defaultValue: meta.default
1702
+ });
1703
+ return cyclic ? lazyGetter({
1704
+ name,
1705
+ body
1706
+ }) : `${objectKey(name)}: ${body}`;
1707
+ });
1489
1708
  }
1490
1709
  /**
1491
1710
  * Zod v4 Mini printer built with `definePrinter`.
@@ -1501,6 +1720,7 @@ function buildZodMiniObjectShape(ctx, node) {
1501
1720
  */
1502
1721
  const printerZodMini = ast.createPrinter((options) => {
1503
1722
  const cyclicSchemaNames = options.cyclicSchemas;
1723
+ const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
1504
1724
  return {
1505
1725
  name: "zod-mini",
1506
1726
  options,
@@ -1573,7 +1793,7 @@ const printerZodMini = ast.createPrinter((options) => {
1573
1793
  enum(node) {
1574
1794
  const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1575
1795
  if (node.namedEnumValues?.length) {
1576
- const literals = nonNullValues.map((v) => `z.literal(${formatDefault(v)})`);
1796
+ const literals = nonNullValues.map((v) => buildLiteral(v));
1577
1797
  if (literals.length === 1) return literals[0];
1578
1798
  return `z.union([${literals.join(", ")}])`;
1579
1799
  }
@@ -1595,32 +1815,40 @@ const printerZodMini = ast.createPrinter((options) => {
1595
1815
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1596
1816
  const isLoose = node.additionalProperties === true;
1597
1817
  const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodMiniObjectShape(this, node)})`;
1598
- const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1599
- if (node.additionalProperties && node.additionalProperties !== true) {
1600
- const catchallType = this.transform(node.additionalProperties);
1601
- if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1602
- return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1603
- }
1604
- if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) return `${recordFn}(${propertyNamesKeySchema}, ${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
1605
- if (node.additionalProperties === true) return objectBase;
1606
- if (isStrict) return objectBase;
1607
- if (patterns.length > 0) {
1608
- const values = patterns.map(([, valueSchema]) => {
1609
- const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
1610
- return valueSchema.nullable ? `z.nullable(${valueType})` : valueType;
1611
- });
1612
- const distinct = [...new Set(values)];
1613
- const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1614
- if (entries.length > 0) return `z.catchall(${objectBase}, ${value})`;
1615
- return `z.record(${patternKeySchemaMini({
1616
- patterns: patterns.map(([pattern]) => pattern),
1617
- regexType: this.options.regexType
1618
- })}, ${value})`;
1619
- }
1620
- return objectBase;
1818
+ return objectConstraints({
1819
+ value: (() => {
1820
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1821
+ if (node.additionalProperties && node.additionalProperties !== true) {
1822
+ const catchallType = this.transform(node.additionalProperties);
1823
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1824
+ return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1825
+ }
1826
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1827
+ const unknownType = this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1828
+ return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1829
+ }
1830
+ if (node.additionalProperties === true) return objectBase;
1831
+ if (isStrict) return objectBase;
1832
+ if (patterns.length > 0) {
1833
+ const values = patterns.map(([, valueSchema]) => {
1834
+ const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1835
+ return valueSchema.nullable ? `z.nullable(${valueType})` : valueType;
1836
+ });
1837
+ const distinct = [...new Set(values)];
1838
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1839
+ if (entries.length > 0) return `z.catchall(${objectBase}, ${value})`;
1840
+ return `z.record(${patternKeySchemaMini({
1841
+ patterns: patterns.map(([pattern]) => pattern),
1842
+ regexType: this.options.regexType
1843
+ })}, ${value})`;
1844
+ }
1845
+ return objectBase;
1846
+ })(),
1847
+ node
1848
+ });
1621
1849
  },
1622
1850
  array(node) {
1623
- const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthChecksMini({
1851
+ const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }))})${lengthChecksMini({
1624
1852
  ...node,
1625
1853
  regexType: this.options.regexType
1626
1854
  })}`;
@@ -1633,7 +1861,7 @@ const printerZodMini = ast.createPrinter((options) => {
1633
1861
  const nodeMembers = node.members ?? [];
1634
1862
  const isDiscriminated = !!node.discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1635
1863
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1636
- const isStrict = node.strategy === "one" && !isXor;
1864
+ const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1637
1865
  const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ output }) => output).filter(Boolean);
1638
1866
  if (members.length === 0) return "";
1639
1867
  if (members.length === 1) return members[0];
@@ -1644,11 +1872,21 @@ const printerZodMini = ast.createPrinter((options) => {
1644
1872
  intersection(node) {
1645
1873
  const members = node.members ?? [];
1646
1874
  if (members.length === 0) return "";
1875
+ const hasSiblingProperties = members.some((member) => member.type === ast.schemaTypes.object && member.properties.length > 0);
1876
+ const transform = (member) => {
1877
+ if (!hasSiblingProperties) return this.transform(member);
1878
+ unionsWithSiblings.add(member);
1879
+ try {
1880
+ return this.transform(member);
1881
+ } finally {
1882
+ unionsWithSiblings.delete(member);
1883
+ }
1884
+ };
1647
1885
  const [first, ...rest] = members;
1648
1886
  if (!first) return "";
1649
- const firstBase = this.transform(first);
1887
+ const firstBase = transform(first);
1650
1888
  if (!firstBase) return "";
1651
- if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `z.extend(${acc}, ${buildZodMiniObjectShape(this, member)})`, firstBase);
1889
+ if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return preserveRequiredProperties(members).slice(1).reduce((acc, member) => `z.extend(${acc}, ${buildZodMiniObjectShape(this, member)})`, firstBase);
1652
1890
  return rest.reduce((acc, member) => {
1653
1891
  const constraint = getMemberConstraint({
1654
1892
  member,
@@ -1657,7 +1895,7 @@ const printerZodMini = ast.createPrinter((options) => {
1657
1895
  numbers: numberChecksMini
1658
1896
  });
1659
1897
  if (constraint) return acc + constraint;
1660
- const transformed = this.transform(member);
1898
+ const transformed = transform(member);
1661
1899
  return transformed ? `z.intersection(${acc}, ${transformed})` : acc;
1662
1900
  }, firstBase);
1663
1901
  }
@@ -1675,8 +1913,7 @@ const printerZodMini = ast.createPrinter((options) => {
1675
1913
  }
1676
1914
  };
1677
1915
  });
1678
- //#endregion
1679
- //#region src/generators/zodGenerator.tsx
1916
+ // src/generators/zodGenerator.tsx
1680
1917
  /**
1681
1918
  * The helper an inferred alias is printed with. `'codec'` reads a request (encode) schema from its
1682
1919
  * input and a response (decode) schema from its output; `true` keeps `z.infer` for both.
@@ -2137,17 +2374,14 @@ const zodGenerator = defineGenerator({
2137
2374
  });
2138
2375
  }
2139
2376
  });
2140
- //#endregion
2141
- //#region kubb-extension.json
2377
+ // kubb-extension.json
2142
2378
  var compatibility = {
2143
- "kubb": ">=5.8.3",
2379
+ "kubb": ">=5.14.6",
2144
2380
  "node": ">=22.22"
2145
2381
  };
2146
- //#endregion
2147
- //#region package.json
2148
- var version = "5.7.2";
2149
- //#endregion
2150
- //#region src/resolvers/resolverZod.ts
2382
+ // package.json
2383
+ var version = "5.7.3";
2384
+ // src/resolvers/resolverZod.ts
2151
2385
  /**
2152
2386
  * Default resolver used by `@kubb/plugin-zod`. Decides the names and file
2153
2387
  * paths for every generated Zod schema. Schemas use camelCase with a
@@ -2193,15 +2427,22 @@ const resolverZod = createResolver({
2193
2427
  response: {
2194
2428
  ...createOperationResponseResolver(),
2195
2429
  error(node) {
2196
- return this.name(`${node.operationId} Error`);
2430
+ const name = this.name(`${node.operationId} Error`);
2431
+ return this.resolveName({
2432
+ key: operationKey(node, "response", "error"),
2433
+ name
2434
+ });
2197
2435
  },
2198
2436
  options(node) {
2199
- return this.schema.type(this.name(`${node.operationId} Options`));
2437
+ const name = this.schema.type(this.name(`${node.operationId} Options`));
2438
+ return this.resolveName({
2439
+ key: operationKey(node, "response", "options"),
2440
+ name
2441
+ });
2200
2442
  }
2201
2443
  }
2202
2444
  });
2203
- //#endregion
2204
- //#region src/plugin.ts
2445
+ // src/plugin.ts
2205
2446
  /**
2206
2447
  * Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
2207
2448
  * cross-plugin dependency references.
@@ -2267,7 +2508,6 @@ const pluginZod = defineExtension({
2267
2508
  };
2268
2509
  }
2269
2510
  });
2270
- //#endregion
2271
2511
  export { pluginZod as default, pluginZod, pluginZodName, printerZod, printerZodMini, resolverZod, zodGenerator };
2272
2512
 
2273
2513
  //# sourceMappingURL=index.js.map