@kubb/plugin-zod 5.7.1 → 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
  */
@@ -809,9 +898,6 @@ function shouldCoerce(coercion, type) {
809
898
  if (coercion === true) return true;
810
899
  return !!coercion[type];
811
900
  }
812
- function propertyNamesOf(node) {
813
- return "propertyNames" in node ? node.propertyNames : void 0;
814
- }
815
901
  /**
816
902
  * Whether the schema node represents a closed or finite set of keys (e.g. enum, literal,
817
903
  * or union of enums/literals).
@@ -832,30 +918,32 @@ function isClosedKeySchema(node) {
832
918
  }
833
919
  /**
834
920
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
835
- * 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
836
922
  * on `.and(…)`.
837
923
  */
838
924
  function isPlainInlineObject(node) {
839
925
  if (node.type !== "object") return false;
926
+ if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
840
927
  if (node.nullable || node.optional || node.nullish) return false;
841
928
  const hasAdditionalProperties = node.additionalProperties !== void 0;
842
929
  const hasPatternProperties = Boolean(node.patternProperties);
843
- return !hasAdditionalProperties && !hasPatternProperties && !propertyNamesOf(node);
930
+ return !hasAdditionalProperties && !hasPatternProperties && !node.propertyNames;
844
931
  }
845
932
  /**
846
933
  * Whether a node renders as a bare Zod object — one that accepts `.extend(…)` and can be a
847
934
  * `z.discriminatedUnion` option. Covers object nodes, `$ref`s that resolve to (or are still
848
935
  * unresolved) objects, single-member unions of an object, and object-composable `allOf`.
849
936
  *
850
- * `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(…)`.
851
938
  */
852
939
  function isObjectSchemaNode(node, cyclicSchemas) {
853
940
  if (node.nullable || node.optional || node.nullish) return false;
854
941
  if (node.type === "object") {
942
+ if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
855
943
  const hasProperties = Boolean(node.properties?.length);
856
944
  const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
857
945
  const hasPatternProperties = Boolean(node.patternProperties);
858
- return !(!hasProperties && (hasTypedAdditionalProperties || hasPatternProperties || propertyNamesOf(node)));
946
+ return !(!hasProperties && (hasTypedAdditionalProperties || hasPatternProperties || node.propertyNames));
859
947
  }
860
948
  if (node.type === "ref") {
861
949
  const refName = ast.resolveRefName(node);
@@ -879,6 +967,49 @@ function isObjectComposableIntersection(node, cyclicSchemas) {
879
967
  const [first, ...rest] = node.members ?? [];
880
968
  return !!first && isObjectSchemaNode(first, cyclicSchemas) && rest.every(isPlainInlineObject);
881
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
+ }
882
1013
  /**
883
1014
  * Format a default value as a code-level literal.
884
1015
  * Arrays and objects keep their contents as a JSON literal, primitives become their string
@@ -925,11 +1056,20 @@ function defaultLiteral(node, value) {
925
1056
  */
926
1057
  function buildEnum(values) {
927
1058
  if (values.every((v) => typeof v === "string")) return `z.enum([${values.map((v) => formatDefault(v)).join(", ")}])`;
928
- const literals = values.map((v) => `z.literal(${formatDefault(v)})`);
1059
+ const literals = values.map((value) => buildLiteral(value));
929
1060
  if (literals.length === 1) return literals[0];
930
1061
  return `z.union([${literals.join(", ")}])`;
931
1062
  }
932
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
+ /**
933
1073
  * Digit pattern for a `type: 'string'` schema that carries an integer `format`, the way ProtoJSON
934
1074
  * encodes 64-bit integers. Returns `undefined` for every other format, and a `pattern` from the
935
1075
  * spec takes precedence over this fallback.
@@ -1012,6 +1152,17 @@ function lengthChecksMini({ min, max, pattern, regexType }) {
1012
1152
  return checks.length ? `.check(${checks.join(", ")})` : "";
1013
1153
  }
1014
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
+ /**
1015
1166
  * Apply nullable / optional / nullish modifiers, an optional `.describe()` call, and an
1016
1167
  * optional `.meta({ examples })` call to a schema value string using the chainable Zod v4 API.
1017
1168
  */
@@ -1101,7 +1252,7 @@ function getMemberConstraint({ member, regexType, lengths, numbers }) {
1101
1252
  /**
1102
1253
  * Builds the `{ key: value, … }` shape shared by the `z.object(...)` and `.extend(...)` renderings.
1103
1254
  */
1104
- function buildObjectShape(ctx, node, modify) {
1255
+ function buildObjectShape(ctx, node, renderProperty) {
1105
1256
  const objectNode = ast.narrowSchema(node, "object");
1106
1257
  if (!objectNode) return "{}";
1107
1258
  const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
@@ -1112,18 +1263,16 @@ function buildObjectShape(ctx, node, modify) {
1112
1263
  const baseOutput = ctx.transform(schema) ?? ctx.transform(ast.factory.createSchema({ type: "unknown" }));
1113
1264
  if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
1114
1265
  return baseOutput;
1115
- }).map(({ name: propName, property, output: baseOutput }) => {
1266
+ }).map(({ name, property, output: value }) => {
1116
1267
  const { schema } = property;
1117
- const value = modify({
1118
- value: baseOutput,
1268
+ return renderProperty({
1269
+ name,
1270
+ value,
1119
1271
  schema,
1120
1272
  required: property.required,
1121
- meta: syncSchemaRef(schema)
1273
+ meta: syncSchemaRef(schema),
1274
+ cyclic: isCyclic(schema)
1122
1275
  });
1123
- return isCyclic(schema) ? lazyGetter({
1124
- name: propName,
1125
- body: value
1126
- }) : `${objectKey(propName)}: ${value}`;
1127
1276
  }));
1128
1277
  }
1129
1278
  /**
@@ -1138,22 +1287,28 @@ function printWithOmit(ctx, node, modify, mini = false) {
1138
1287
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
1139
1288
  const unwrap = omitUnwrapChain(node, mini ? "._zod.def.innerType" : ".unwrap()");
1140
1289
  const mask = `{ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }`;
1141
- 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
+ };
1142
1298
  const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
1143
1299
  if (lazyMatch) return `z.lazy(() => ${omit(lazyMatch[1])})`;
1144
1300
  return omit(transformed);
1145
1301
  })(), meta);
1146
1302
  }
1147
- //#endregion
1148
- //#region src/printers/printerZod.ts
1303
+ // src/printers/printerZod.ts
1149
1304
  function strictOneOfMember(member, node, cyclicSchemas) {
1150
- if (node.type === "ref") {
1305
+ if (node.type === ast.schemaTypes.ref) {
1151
1306
  if (member.startsWith("z.lazy(")) return member;
1152
1307
  const refName = ast.resolveRefName(node);
1153
1308
  if (refName && cyclicSchemas?.has(refName)) return member;
1154
1309
  const schema = syncSchemaRef(node);
1155
1310
  if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
1156
- 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()`;
1157
1312
  }
1158
1313
  return member;
1159
1314
  }
@@ -1161,28 +1316,70 @@ function strictOneOfMember(member, node, cyclicSchemas) {
1161
1316
  * Builds the `{ key: value, … }` shape for an object node with the chainable modifiers.
1162
1317
  */
1163
1318
  function buildZodObjectShape(ctx, node) {
1164
- return buildObjectShape(ctx, node, ({ value, schema, required, meta }) => applyModifiers({
1165
- value,
1166
- schema,
1167
- nullable: meta.nullable,
1168
- optional: schema.optional || required === false,
1169
- nullish: schema.nullish,
1170
- defaultValue: meta.default,
1171
- description: schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description,
1172
- examples: meta.examples
1173
- }));
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;
1174
1371
  }
1175
1372
  /**
1176
1373
  * Types the direction probe skips. They delegate to their children rather than reading
1177
1374
  * `direction` themselves, so {@link containsDirectionalNode} walks into the children instead.
1178
1375
  */
1179
1376
  const CONTAINER_TYPES = /* @__PURE__ */ new Set([
1180
- "object",
1181
- "array",
1182
- "tuple",
1183
- "union",
1184
- "intersection",
1185
- "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
1186
1383
  ]);
1187
1384
  /**
1188
1385
  * Runs the node's effective handler (a `printer.nodes` override, else the built-in) once per
@@ -1214,7 +1411,7 @@ function variesByDirection({ node, printerOptions }) {
1214
1411
  */
1215
1412
  function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */ new Set() }) {
1216
1413
  if (!node) return false;
1217
- if (node.type === "ref") {
1414
+ if (node.type === ast.schemaTypes.ref) {
1218
1415
  if (!node.ref) return false;
1219
1416
  const refName = extractRefName(node.ref);
1220
1417
  if (refName) {
@@ -1222,7 +1419,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1222
1419
  seen.add(refName);
1223
1420
  }
1224
1421
  const resolved = syncSchemaRef(node);
1225
- if (resolved.type === "ref") return false;
1422
+ if (resolved.type === ast.schemaTypes.ref) return false;
1226
1423
  return containsDirectionalNode({
1227
1424
  node: resolved,
1228
1425
  printerOptions,
@@ -1238,7 +1435,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1238
1435
  if ("items" in node && node.items) children.push(...node.items);
1239
1436
  if ("members" in node && node.members) children.push(...node.members);
1240
1437
  if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
1241
- const propertyNames = propertyNamesOf(node);
1438
+ const propertyNames = node.type === ast.schemaTypes.object ? node.propertyNames : void 0;
1242
1439
  if (propertyNames) children.push(propertyNames);
1243
1440
  return children.some((child) => containsDirectionalNode({
1244
1441
  node: child,
@@ -1250,7 +1447,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1250
1447
  * Names of the `$ref` schemas the generator should route to their input (encode) variant.
1251
1448
  */
1252
1449
  function collectDirectionalRefNames({ node, printerOptions }) {
1253
- 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({
1254
1451
  node: n,
1255
1452
  printerOptions
1256
1453
  }) ? ast.resolveRefName(n) ?? void 0 : void 0 });
@@ -1342,7 +1539,7 @@ const scalarNodes = {
1342
1539
  enum(node) {
1343
1540
  const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1344
1541
  if (node.namedEnumValues?.length) {
1345
- const literals = nonNullValues.map((v) => `z.literal(${formatDefault(v)})`);
1542
+ const literals = nonNullValues.map((v) => buildLiteral(v));
1346
1543
  if (literals.length === 1) return literals[0];
1347
1544
  return `z.union([${literals.join(", ")}])`;
1348
1545
  }
@@ -1363,6 +1560,7 @@ const scalarNodes = {
1363
1560
  */
1364
1561
  const printerZod = ast.createPrinter((options) => {
1365
1562
  const cyclicSchemaNames = options.cyclicSchemas;
1563
+ const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
1366
1564
  return {
1367
1565
  name: "zod",
1368
1566
  options,
@@ -1383,42 +1581,45 @@ const printerZod = ast.createPrinter((options) => {
1383
1581
  object(node) {
1384
1582
  const entries = node.properties ?? [];
1385
1583
  const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1386
- const propertyNamesNode = propertyNamesOf(node);
1584
+ const propertyNamesNode = node.propertyNames;
1387
1585
  const propertyNamesKeySchema = propertyNamesNode ? this.transform(propertyNamesNode) : null;
1388
1586
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1389
1587
  const isLoose = node.additionalProperties === true;
1390
1588
  const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodObjectShape(this, node)})`;
1391
- return (() => {
1392
- const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1393
- if (node.additionalProperties && node.additionalProperties !== true) {
1394
- const catchallType = this.transform(node.additionalProperties);
1395
- if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1396
- return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1397
- }
1398
- if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1399
- const unknownType = this.transform(ast.factory.createSchema({ type: "unknown" }));
1400
- return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1401
- }
1402
- if (node.additionalProperties === true) return objectBase;
1403
- if (isStrict) return objectBase;
1404
- if (patterns.length > 0) {
1405
- const values = patterns.map(([, valueSchema]) => {
1406
- const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
1407
- return valueSchema.nullable ? `${valueType}.nullable()` : valueType;
1408
- });
1409
- const distinct = [...new Set(values)];
1410
- const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1411
- if (entries.length > 0) return `${objectBase}.catchall(${value})`;
1412
- return `z.record(${patternKeySchema({
1413
- patterns: patterns.map(([pattern]) => pattern),
1414
- regexType: this.options.regexType
1415
- })}, ${value})`;
1416
- }
1417
- return objectBase;
1418
- })();
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
+ });
1419
1620
  },
1420
1621
  array(node) {
1421
- 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({
1422
1623
  ...node,
1423
1624
  regexType: this.options.regexType
1424
1625
  })}`;
@@ -1431,7 +1632,7 @@ const printerZod = ast.createPrinter((options) => {
1431
1632
  const nodeMembers = node.members ?? [];
1432
1633
  const isDiscriminated = !!node.discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1433
1634
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1434
- const isStrict = node.strategy === "one" && !isXor;
1635
+ const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1435
1636
  const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ schema, output }) => output && isStrict ? strictOneOfMember(output, schema, cyclicSchemaNames) : output).filter(Boolean);
1436
1637
  if (members.length === 0) return "";
1437
1638
  if (members.length === 1) return members[0];
@@ -1442,11 +1643,21 @@ const printerZod = ast.createPrinter((options) => {
1442
1643
  intersection(node) {
1443
1644
  const members = node.members ?? [];
1444
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
+ };
1445
1656
  const [first, ...rest] = members;
1446
1657
  if (!first) return "";
1447
- const firstBase = this.transform(first);
1658
+ const firstBase = transform(first);
1448
1659
  if (!firstBase) return "";
1449
- 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);
1450
1661
  return rest.reduce((acc, member) => {
1451
1662
  const constraint = getMemberConstraint({
1452
1663
  member,
@@ -1455,7 +1666,7 @@ const printerZod = ast.createPrinter((options) => {
1455
1666
  numbers: numberConstraints
1456
1667
  });
1457
1668
  if (constraint) return acc + constraint;
1458
- const transformed = this.transform(member);
1669
+ const transformed = transform(member);
1459
1670
  return transformed ? `${acc}.and(${transformed})` : acc;
1460
1671
  }, firstBase);
1461
1672
  }
@@ -1475,20 +1686,25 @@ const printerZod = ast.createPrinter((options) => {
1475
1686
  }
1476
1687
  };
1477
1688
  });
1478
- //#endregion
1479
- //#region src/printers/printerZodMini.ts
1689
+ // src/printers/printerZodMini.ts
1480
1690
  /**
1481
1691
  * Builds the `{ key: value, … }` shape for an object node with the functional `zod/mini` modifiers.
1482
1692
  */
1483
1693
  function buildZodMiniObjectShape(ctx, node) {
1484
- return buildObjectShape(ctx, node, ({ value, schema, required, meta }) => applyMiniModifiers({
1485
- value,
1486
- schema,
1487
- nullable: meta.nullable,
1488
- optional: schema.optional || required === false,
1489
- nullish: schema.nullish,
1490
- defaultValue: meta.default
1491
- }));
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
+ });
1492
1708
  }
1493
1709
  /**
1494
1710
  * Zod v4 Mini printer built with `definePrinter`.
@@ -1504,6 +1720,7 @@ function buildZodMiniObjectShape(ctx, node) {
1504
1720
  */
1505
1721
  const printerZodMini = ast.createPrinter((options) => {
1506
1722
  const cyclicSchemaNames = options.cyclicSchemas;
1723
+ const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
1507
1724
  return {
1508
1725
  name: "zod-mini",
1509
1726
  options,
@@ -1576,7 +1793,7 @@ const printerZodMini = ast.createPrinter((options) => {
1576
1793
  enum(node) {
1577
1794
  const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1578
1795
  if (node.namedEnumValues?.length) {
1579
- const literals = nonNullValues.map((v) => `z.literal(${formatDefault(v)})`);
1796
+ const literals = nonNullValues.map((v) => buildLiteral(v));
1580
1797
  if (literals.length === 1) return literals[0];
1581
1798
  return `z.union([${literals.join(", ")}])`;
1582
1799
  }
@@ -1593,37 +1810,45 @@ const printerZodMini = ast.createPrinter((options) => {
1593
1810
  object(node) {
1594
1811
  const entries = node.properties ?? [];
1595
1812
  const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1596
- const propertyNamesNode = propertyNamesOf(node);
1813
+ const propertyNamesNode = node.propertyNames;
1597
1814
  const propertyNamesKeySchema = propertyNamesNode ? this.transform(propertyNamesNode) : null;
1598
1815
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1599
1816
  const isLoose = node.additionalProperties === true;
1600
1817
  const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodMiniObjectShape(this, node)})`;
1601
- const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1602
- if (node.additionalProperties && node.additionalProperties !== true) {
1603
- const catchallType = this.transform(node.additionalProperties);
1604
- if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1605
- return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1606
- }
1607
- if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) return `${recordFn}(${propertyNamesKeySchema}, ${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
1608
- if (node.additionalProperties === true) return objectBase;
1609
- if (isStrict) return objectBase;
1610
- if (patterns.length > 0) {
1611
- const values = patterns.map(([, valueSchema]) => {
1612
- const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
1613
- return valueSchema.nullable ? `z.nullable(${valueType})` : valueType;
1614
- });
1615
- const distinct = [...new Set(values)];
1616
- const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1617
- if (entries.length > 0) return `z.catchall(${objectBase}, ${value})`;
1618
- return `z.record(${patternKeySchemaMini({
1619
- patterns: patterns.map(([pattern]) => pattern),
1620
- regexType: this.options.regexType
1621
- })}, ${value})`;
1622
- }
1623
- 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
+ });
1624
1849
  },
1625
1850
  array(node) {
1626
- 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({
1627
1852
  ...node,
1628
1853
  regexType: this.options.regexType
1629
1854
  })}`;
@@ -1636,7 +1861,7 @@ const printerZodMini = ast.createPrinter((options) => {
1636
1861
  const nodeMembers = node.members ?? [];
1637
1862
  const isDiscriminated = !!node.discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1638
1863
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1639
- const isStrict = node.strategy === "one" && !isXor;
1864
+ const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1640
1865
  const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ output }) => output).filter(Boolean);
1641
1866
  if (members.length === 0) return "";
1642
1867
  if (members.length === 1) return members[0];
@@ -1647,11 +1872,21 @@ const printerZodMini = ast.createPrinter((options) => {
1647
1872
  intersection(node) {
1648
1873
  const members = node.members ?? [];
1649
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
+ };
1650
1885
  const [first, ...rest] = members;
1651
1886
  if (!first) return "";
1652
- const firstBase = this.transform(first);
1887
+ const firstBase = transform(first);
1653
1888
  if (!firstBase) return "";
1654
- 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);
1655
1890
  return rest.reduce((acc, member) => {
1656
1891
  const constraint = getMemberConstraint({
1657
1892
  member,
@@ -1660,7 +1895,7 @@ const printerZodMini = ast.createPrinter((options) => {
1660
1895
  numbers: numberChecksMini
1661
1896
  });
1662
1897
  if (constraint) return acc + constraint;
1663
- const transformed = this.transform(member);
1898
+ const transformed = transform(member);
1664
1899
  return transformed ? `z.intersection(${acc}, ${transformed})` : acc;
1665
1900
  }, firstBase);
1666
1901
  }
@@ -1678,8 +1913,7 @@ const printerZodMini = ast.createPrinter((options) => {
1678
1913
  }
1679
1914
  };
1680
1915
  });
1681
- //#endregion
1682
- //#region src/generators/zodGenerator.tsx
1916
+ // src/generators/zodGenerator.tsx
1683
1917
  /**
1684
1918
  * The helper an inferred alias is printed with. `'codec'` reads a request (encode) schema from its
1685
1919
  * input and a response (decode) schema from its output; `true` keeps `z.infer` for both.
@@ -2140,17 +2374,14 @@ const zodGenerator = defineGenerator({
2140
2374
  });
2141
2375
  }
2142
2376
  });
2143
- //#endregion
2144
- //#region kubb-extension.json
2377
+ // kubb-extension.json
2145
2378
  var compatibility = {
2146
- "kubb": ">=5.8.3",
2379
+ "kubb": ">=5.14.6",
2147
2380
  "node": ">=22.22"
2148
2381
  };
2149
- //#endregion
2150
- //#region package.json
2151
- var version = "5.7.1";
2152
- //#endregion
2153
- //#region src/resolvers/resolverZod.ts
2382
+ // package.json
2383
+ var version = "5.7.3";
2384
+ // src/resolvers/resolverZod.ts
2154
2385
  /**
2155
2386
  * Default resolver used by `@kubb/plugin-zod`. Decides the names and file
2156
2387
  * paths for every generated Zod schema. Schemas use camelCase with a
@@ -2196,15 +2427,22 @@ const resolverZod = createResolver({
2196
2427
  response: {
2197
2428
  ...createOperationResponseResolver(),
2198
2429
  error(node) {
2199
- 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
+ });
2200
2435
  },
2201
2436
  options(node) {
2202
- 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
+ });
2203
2442
  }
2204
2443
  }
2205
2444
  });
2206
- //#endregion
2207
- //#region src/plugin.ts
2445
+ // src/plugin.ts
2208
2446
  /**
2209
2447
  * Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
2210
2448
  * cross-plugin dependency references.
@@ -2270,7 +2508,6 @@ const pluginZod = defineExtension({
2270
2508
  };
2271
2509
  }
2272
2510
  });
2273
- //#endregion
2274
2511
  export { pluginZod as default, pluginZod, pluginZodName, printerZod, printerZodMini, resolverZod, zodGenerator };
2275
2512
 
2276
2513
  //# sourceMappingURL=index.js.map