@kubb/plugin-zod 5.7.2 → 5.7.4

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
- import { Diagnostics, Resolver, addGenerator, ast, containsCircularRef, createResolver, defineExtension, defineGenerator, extractRefName, setMacros, setResolver, syncSchemaRef } from "kubb/kit";
1
+ import { Diagnostics, Resolver, addGenerator, ast, camelCase, containsCircularRef, createResolver, defineExtension, defineGenerator, extractRefName, pascalCase, 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,58 +255,7 @@ function getOasAdapter(adapter) {
226
255
  });
227
256
  return adapter;
228
257
  }
229
- //#endregion
230
- //#region ../../internals/utils/src/casing.ts
231
- /**
232
- * Shared implementation for camelCase and PascalCase conversion.
233
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
234
- * and capitalizes each word according to `pascal`.
235
- *
236
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
237
- */
238
- function convert(text, pascal) {
239
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
240
- if (word.length > 1 && word === word.toUpperCase()) return word;
241
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
242
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
243
- }
244
- const cache = /* @__PURE__ */ new Map();
245
- const cacheLimit = 1e4;
246
- function toCamelOrPascal(text, pascal) {
247
- const key = `${pascal ? "P" : "c"}${text}`;
248
- const cached = cache.get(key);
249
- if (cached !== void 0) return cached;
250
- const result = convert(text, pascal);
251
- if (cache.size >= cacheLimit) cache.clear();
252
- cache.set(key, result);
253
- return result;
254
- }
255
- /**
256
- * Converts `text` to camelCase.
257
- *
258
- * @example Word boundaries
259
- * `camelCase('hello-world') // 'helloWorld'`
260
- *
261
- * @example With a prefix
262
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
263
- */
264
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
265
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
266
- }
267
- /**
268
- * Converts `text` to PascalCase.
269
- *
270
- * @example Word boundaries
271
- * `pascalCase('hello-world') // 'HelloWorld'`
272
- *
273
- * @example With a suffix
274
- * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
275
- */
276
- function pascalCase(text, { prefix = "", suffix = "" } = {}) {
277
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
278
- }
279
- //#endregion
280
- //#region ../../internals/utils/src/reserved.ts
258
+ // ../../internals/utils/src/reserved.ts
281
259
  /**
282
260
  * JavaScript and Java reserved words.
283
261
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
@@ -285,6 +263,7 @@ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
285
263
  const reservedWords = /* @__PURE__ */ new Set([
286
264
  "abstract",
287
265
  "arguments",
266
+ "await",
288
267
  "boolean",
289
268
  "break",
290
269
  "byte",
@@ -415,8 +394,7 @@ function ensureValidVarName(name) {
415
394
  function isIdentifier(name) {
416
395
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
417
396
  }
418
- //#endregion
419
- //#region ../../internals/utils/src/strings.ts
397
+ // ../../internals/utils/src/strings.ts
420
398
  /**
421
399
  * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
422
400
  * any backslash or single quote in the content.
@@ -465,6 +443,34 @@ function stringify(value) {
465
443
  if (value === void 0 || value === null) return "''";
466
444
  return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
467
445
  }
446
+ const NEEDS_UNICODE = /(?<!\\)(?:\\\\)*\\[pPu]\{|[\u{10000}-\u{10FFFF}]/u;
447
+ const KEPT_ESCAPE = /[A-Za-z0-9^$\\.*+?()[\]{}|/]/;
448
+ /**
449
+ * Drops the backslash from escapes that the `u` flag rejects: an escaped punctuation character that is
450
+ * not a syntax character, and `\-` outside a class. Both match the same character without the backslash.
451
+ */
452
+ function dropIdentityEscapes(pattern) {
453
+ let inClass = false;
454
+ return pattern.replace(/\\(.)|[[\]]/gs, (match, escaped) => {
455
+ if (escaped === void 0) {
456
+ inClass = match === "[";
457
+ return match;
458
+ }
459
+ return KEPT_ESCAPE.test(escaped) || escaped === "-" && inClass ? match : escaped;
460
+ });
461
+ }
462
+ /**
463
+ * Builds the `RegExp` for a pattern, adding the `u` flag when it uses `\p{...}`, `\P{...}` or `\u{...}`, or
464
+ * holds a character outside the BMP. A pattern that is not valid under `u` keeps the flags it came with.
465
+ */
466
+ function toRegExp(pattern, flags) {
467
+ if (flags?.includes("u") || !NEEDS_UNICODE.test(pattern)) return new RegExp(pattern, flags);
468
+ try {
469
+ return new RegExp(dropIdentityEscapes(pattern), `${flags ?? ""}u`);
470
+ } catch {
471
+ return new RegExp(pattern, flags);
472
+ }
473
+ }
468
474
  /**
469
475
  * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
470
476
  * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
@@ -481,13 +487,11 @@ function toRegExpString(text, func = "RegExp") {
481
487
  const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
482
488
  const replacementTarget = match?.[1] ?? "";
483
489
  const matchedFlags = match?.[2];
484
- const cleaned = raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, "");
485
- const { source, flags } = new RegExp(cleaned, matchedFlags);
490
+ const { source, flags } = toRegExp(raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, ""), matchedFlags);
486
491
  if (func === null) return `/${source}/${flags}`;
487
492
  return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
488
493
  }
489
- //#endregion
490
- //#region ../../internals/utils/src/codegen.ts
494
+ // ../../internals/utils/src/codegen.ts
491
495
  const INDENT = " ";
492
496
  /**
493
497
  * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
@@ -556,8 +560,7 @@ function buildList(items, brackets = ["[", "]"]) {
556
560
  function lazyGetter({ name, body }) {
557
561
  return `get ${objectKey(name)}() { return ${body} }`;
558
562
  }
559
- //#endregion
560
- //#region ../../internals/utils/src/fs.ts
563
+ // ../../internals/utils/src/fs.ts
561
564
  /**
562
565
  * Builds a nested file path from a dotted name. Splits on dots that precede a letter
563
566
  * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
@@ -581,17 +584,24 @@ function toFilePath(name, caseLast = camelCase) {
581
584
  const parts = name.split(/\.(?=[a-zA-Z])/);
582
585
  return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
583
586
  }
584
- //#endregion
585
- //#region ../../internals/shared/src/resolver.ts
587
+ // ../../internals/shared/src/resolver.ts
586
588
  /**
587
- * Resolves a single operation parameter name with the
588
- * `<operationId> <in> <name>` template.
589
+ * Builds the allocation key for a declaration an operation owns. `scope` names which declaration
590
+ * it is, so two of them never share an allocation even when a custom resolver gives both the same
591
+ * name. Leave `scope` empty for the operation's own declaration.
589
592
  *
590
593
  * @example
591
- * `operationParamName.call(resolver, node, param) // → 'DeletePetPathPetId'`
594
+ * ```ts
595
+ * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
596
+ * ```
592
597
  */
593
- function operationParamName(node, param) {
594
- return this.name(`${node.operationId} ${param.in} ${param.name}`);
598
+ function operationKey(node, ...scope) {
599
+ return [
600
+ "operation",
601
+ node.method,
602
+ node.path,
603
+ ...scope.map(String)
604
+ ];
595
605
  }
596
606
  /**
597
607
  * Builds the shared `param` namespace. Spread the result into `createResolver`
@@ -604,15 +614,33 @@ function operationParamName(node, param) {
604
614
  */
605
615
  function createOperationParamResolver() {
606
616
  return {
607
- name: operationParamName,
617
+ name(node, param) {
618
+ const name = this.name(`${node.operationId} ${param.in} ${param.name}`);
619
+ return this.resolveName({
620
+ key: operationKey(node, "param", "name", param.in, param.name),
621
+ name
622
+ });
623
+ },
608
624
  path(node) {
609
- return this.name(`${node.operationId} Path`);
625
+ const name = this.name(`${node.operationId} Path`);
626
+ return this.resolveName({
627
+ key: operationKey(node, "param", "path"),
628
+ name
629
+ });
610
630
  },
611
631
  query(node) {
612
- return this.name(`${node.operationId} Query`);
632
+ const name = this.name(`${node.operationId} Query`);
633
+ return this.resolveName({
634
+ key: operationKey(node, "param", "query"),
635
+ name
636
+ });
613
637
  },
614
638
  headers(node) {
615
- return this.name(`${node.operationId} Headers`);
639
+ const name = this.name(`${node.operationId} Headers`);
640
+ return this.resolveName({
641
+ key: operationKey(node, "param", "headers"),
642
+ name
643
+ });
616
644
  }
617
645
  };
618
646
  }
@@ -629,16 +657,32 @@ function createOperationParamResolver() {
629
657
  function createOperationResponseResolver() {
630
658
  return {
631
659
  status(node, statusCode) {
632
- return this.name(`${node.operationId} Status ${statusCode}`);
660
+ const name = this.name(`${node.operationId} Status ${statusCode}`);
661
+ return this.resolveName({
662
+ key: operationKey(node, "response", "status", statusCode),
663
+ name
664
+ });
633
665
  },
634
666
  body(node) {
635
- return this.name(`${node.operationId} Body`);
667
+ const name = this.name(`${node.operationId} Body`);
668
+ return this.resolveName({
669
+ key: operationKey(node, "response", "body"),
670
+ name
671
+ });
636
672
  },
637
673
  responses(node) {
638
- return this.name(`${node.operationId} Responses`);
674
+ const name = this.name(`${node.operationId} Responses`);
675
+ return this.resolveName({
676
+ key: operationKey(node, "response", "responses"),
677
+ name
678
+ });
639
679
  },
640
680
  response(node) {
641
- return this.name(`${node.operationId} Response`);
681
+ const name = this.name(`${node.operationId} Response`);
682
+ return this.resolveName({
683
+ key: operationKey(node, "response", "response"),
684
+ name
685
+ });
642
686
  }
643
687
  };
644
688
  }
@@ -653,11 +697,10 @@ function createOperationResponseResolver() {
653
697
  */
654
698
  function createCasedFile(caseLast) {
655
699
  return { baseName({ name, extname }) {
656
- return `${toFilePath(name, caseLast)}${extname}`;
700
+ return this.memo(`file:${name}\0${extname}`, () => `${toFilePath(name, caseLast)}${extname}`);
657
701
  } };
658
702
  }
659
- //#endregion
660
- //#region ../../internals/shared/src/group.ts
703
+ // ../../internals/shared/src/group.ts
661
704
  /**
662
705
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
663
706
  * shared default naming so every plugin groups output consistently:
@@ -687,8 +730,7 @@ function createGroupConfig(group) {
687
730
  name: group.name ? group.name : defaultName
688
731
  };
689
732
  }
690
- //#endregion
691
- //#region ../../internals/shared/src/schemaTraversal.ts
733
+ // ../../internals/shared/src/schemaTraversal.ts
692
734
  /**
693
735
  * Maps each property of an object schema to its transformed output. Pairs every result with the
694
736
  * original property so the printer keeps full control over modifiers, getters, and key syntax.
@@ -726,8 +768,7 @@ function mapSchemaItems(node, transform) {
726
768
  output: transform(schema)
727
769
  }));
728
770
  }
729
- //#endregion
730
- //#region src/components/Zod.tsx
771
+ // src/components/Zod.tsx
731
772
  function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuards, isName, assertName, mini, cyclic, compile }) {
732
773
  const output = printer.print(node);
733
774
  const printerImports = printer.drainImports();
@@ -799,8 +840,7 @@ function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuar
799
840
  })
800
841
  ] });
801
842
  }
802
- //#endregion
803
- //#region src/utils.ts
843
+ // src/utils.ts
804
844
  /**
805
845
  * Returns `true` when the given coercion option enables coercion for the specified type.
806
846
  */
@@ -829,11 +869,12 @@ function isClosedKeySchema(node) {
829
869
  }
830
870
  /**
831
871
  * 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
872
+ * argument. Property counts, a catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
833
873
  * on `.and(…)`.
834
874
  */
835
875
  function isPlainInlineObject(node) {
836
876
  if (node.type !== "object") return false;
877
+ if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
837
878
  if (node.nullable || node.optional || node.nullish) return false;
838
879
  const hasAdditionalProperties = node.additionalProperties !== void 0;
839
880
  const hasPatternProperties = Boolean(node.patternProperties);
@@ -844,11 +885,12 @@ function isPlainInlineObject(node) {
844
885
  * `z.discriminatedUnion` option. Covers object nodes, `$ref`s that resolve to (or are still
845
886
  * unresolved) objects, single-member unions of an object, and object-composable `allOf`.
846
887
  *
847
- * `cyclicSchemas` bounds the recursion: a ref into a cycle renders as `z.lazy(…)`, not an object.
888
+ * Property counts render as a pipe. A ref into `cyclicSchemas` renders as `z.lazy(…)`.
848
889
  */
849
890
  function isObjectSchemaNode(node, cyclicSchemas) {
850
891
  if (node.nullable || node.optional || node.nullish) return false;
851
892
  if (node.type === "object") {
893
+ if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
852
894
  const hasProperties = Boolean(node.properties?.length);
853
895
  const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
854
896
  const hasPatternProperties = Boolean(node.patternProperties);
@@ -876,6 +918,49 @@ function isObjectComposableIntersection(node, cyclicSchemas) {
876
918
  const [first, ...rest] = node.members ?? [];
877
919
  return !!first && isObjectSchemaNode(first, cyclicSchemas) && rest.every(isPlainInlineObject);
878
920
  }
921
+ function preserveRequiredProperties(members) {
922
+ const hasRequiredProperty = ({ node, propertyName, seen = /* @__PURE__ */ new Set() }) => {
923
+ if (seen.has(node)) return false;
924
+ seen.add(node);
925
+ const resolved = syncSchemaRef(node);
926
+ const composition = ast.narrowSchema(resolved, ast.schemaTypes.intersection) ?? ast.narrowSchema(resolved, ast.schemaTypes.union);
927
+ if (composition) {
928
+ if (composition.type === ast.schemaTypes.union && composition.members?.length !== 1) return false;
929
+ return composition.members?.some((child) => hasRequiredProperty({
930
+ node: child,
931
+ propertyName,
932
+ seen
933
+ })) ?? false;
934
+ }
935
+ return ast.resolveSchemaProperties({
936
+ node: resolved,
937
+ propertyName
938
+ }).some((property) => property.required !== false && !property.schema.optional && !property.schema.nullish);
939
+ };
940
+ return members.map((member, index) => {
941
+ const object = ast.narrowSchema(member, ast.schemaTypes.object);
942
+ if (!object || index === 0) return member;
943
+ return {
944
+ ...object,
945
+ properties: object.properties.map((property) => {
946
+ if (!members.slice(0, index).some((base) => hasRequiredProperty({
947
+ node: base,
948
+ propertyName: property.name
949
+ }))) return property;
950
+ return {
951
+ ...property,
952
+ required: true,
953
+ schema: {
954
+ ...property.schema,
955
+ optional: false,
956
+ nullish: false,
957
+ nullable: property.schema.nullable || property.schema.nullish
958
+ }
959
+ };
960
+ })
961
+ };
962
+ });
963
+ }
879
964
  /**
880
965
  * Format a default value as a code-level literal.
881
966
  * Arrays and objects keep their contents as a JSON literal, primitives become their string
@@ -922,11 +1007,20 @@ function defaultLiteral(node, value) {
922
1007
  */
923
1008
  function buildEnum(values) {
924
1009
  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)})`);
1010
+ const literals = values.map((value) => buildLiteral(value));
926
1011
  if (literals.length === 1) return literals[0];
927
1012
  return `z.union([${literals.join(", ")}])`;
928
1013
  }
929
1014
  /**
1015
+ * Preserves the structure of JSON const values in both Zod APIs.
1016
+ */
1017
+ function buildLiteral(value) {
1018
+ if (value === null) return "z.null()";
1019
+ if (Array.isArray(value)) return `z.tuple([${value.map((item) => buildLiteral(item)).join(", ")}])`;
1020
+ if (typeof value === "object") return `z.strictObject({${Object.entries(value).map(([name, item]) => `${JSON.stringify(name)}: ${buildLiteral(item)}`).join(", ")}})`;
1021
+ return `z.literal(${formatDefault(value)})`;
1022
+ }
1023
+ /**
930
1024
  * Digit pattern for a `type: 'string'` schema that carries an integer `format`, the way ProtoJSON
931
1025
  * encodes 64-bit integers. Returns `undefined` for every other format, and a `pattern` from the
932
1026
  * spec takes precedence over this fallback.
@@ -1009,6 +1103,17 @@ function lengthChecksMini({ min, max, pattern, regexType }) {
1009
1103
  return checks.length ? `.check(${checks.join(", ")})` : "";
1010
1104
  }
1011
1105
  /**
1106
+ * Checks property counts before object parsing strips unknown keys or inserts defaults.
1107
+ */
1108
+ function objectConstraints({ value, node }) {
1109
+ if (node.type !== "object") return value;
1110
+ const checks = [];
1111
+ const skip = "typeof value !== \"object\" || value === null || Array.isArray(value)";
1112
+ 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" })`);
1113
+ 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" })`);
1114
+ return checks.length ? `z.pipe(z.unknown().check(${checks.join(", ")}), ${value})` : value;
1115
+ }
1116
+ /**
1012
1117
  * Apply nullable / optional / nullish modifiers, an optional `.describe()` call, and an
1013
1118
  * optional `.meta({ examples })` call to a schema value string using the chainable Zod v4 API.
1014
1119
  */
@@ -1098,7 +1203,7 @@ function getMemberConstraint({ member, regexType, lengths, numbers }) {
1098
1203
  /**
1099
1204
  * Builds the `{ key: value, … }` shape shared by the `z.object(...)` and `.extend(...)` renderings.
1100
1205
  */
1101
- function buildObjectShape(ctx, node, modify) {
1206
+ function buildObjectShape(ctx, node, renderProperty) {
1102
1207
  const objectNode = ast.narrowSchema(node, "object");
1103
1208
  if (!objectNode) return "{}";
1104
1209
  const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
@@ -1109,18 +1214,16 @@ function buildObjectShape(ctx, node, modify) {
1109
1214
  const baseOutput = ctx.transform(schema) ?? ctx.transform(ast.factory.createSchema({ type: "unknown" }));
1110
1215
  if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
1111
1216
  return baseOutput;
1112
- }).map(({ name: propName, property, output: baseOutput }) => {
1217
+ }).map(({ name, property, output: value }) => {
1113
1218
  const { schema } = property;
1114
- const value = modify({
1115
- value: baseOutput,
1219
+ return renderProperty({
1220
+ name,
1221
+ value,
1116
1222
  schema,
1117
1223
  required: property.required,
1118
- meta: syncSchemaRef(schema)
1224
+ meta: syncSchemaRef(schema),
1225
+ cyclic: isCyclic(schema)
1119
1226
  });
1120
- return isCyclic(schema) ? lazyGetter({
1121
- name: propName,
1122
- body: value
1123
- }) : `${objectKey(propName)}: ${value}`;
1124
1227
  }));
1125
1228
  }
1126
1229
  /**
@@ -1135,22 +1238,28 @@ function printWithOmit(ctx, node, modify, mini = false) {
1135
1238
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
1136
1239
  const unwrap = omitUnwrapChain(node, mini ? "._zod.def.innerType" : ".unwrap()");
1137
1240
  const mask = `{ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }`;
1138
- const omit = (target) => mini ? `z.omit(${target}${unwrap}, ${mask})` : `${target}${unwrap}.omit(${mask})`;
1241
+ const omit = (target) => {
1242
+ const inner = `${target}${unwrap}`;
1243
+ const hasObjectConstraints = meta.type === "object" && (meta.minProperties !== void 0 || meta.maxProperties !== void 0);
1244
+ const pipe = mini ? `${inner}._zod.def` : inner;
1245
+ const object = hasObjectConstraints ? `${pipe}.out` : inner;
1246
+ const omitted = mini ? `z.omit(${object}, ${mask})` : `${object}.omit(${mask})`;
1247
+ return hasObjectConstraints ? `z.pipe(${pipe}.in, ${omitted})` : omitted;
1248
+ };
1139
1249
  const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
1140
1250
  if (lazyMatch) return `z.lazy(() => ${omit(lazyMatch[1])})`;
1141
1251
  return omit(transformed);
1142
1252
  })(), meta);
1143
1253
  }
1144
- //#endregion
1145
- //#region src/printers/printerZod.ts
1254
+ // src/printers/printerZod.ts
1146
1255
  function strictOneOfMember(member, node, cyclicSchemas) {
1147
- if (node.type === "ref") {
1256
+ if (node.type === ast.schemaTypes.ref) {
1148
1257
  if (member.startsWith("z.lazy(")) return member;
1149
1258
  const refName = ast.resolveRefName(node);
1150
1259
  if (refName && cyclicSchemas?.has(refName)) return member;
1151
1260
  const schema = syncSchemaRef(node);
1152
1261
  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()`;
1262
+ if (schema.type === ast.schemaTypes.object && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
1154
1263
  }
1155
1264
  return member;
1156
1265
  }
@@ -1158,28 +1267,70 @@ function strictOneOfMember(member, node, cyclicSchemas) {
1158
1267
  * Builds the `{ key: value, … }` shape for an object node with the chainable modifiers.
1159
1268
  */
1160
1269
  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
- }));
1270
+ return buildObjectShape(ctx, node, ({ name, value, schema, required, meta, cyclic }) => {
1271
+ const body = applyModifiers({
1272
+ value,
1273
+ schema,
1274
+ nullable: meta.nullable,
1275
+ optional: schema.optional || required === false,
1276
+ nullish: schema.nullish,
1277
+ defaultValue: meta.default,
1278
+ description: schema.type !== ast.schemaTypes.ref && meta.type === ast.schemaTypes.ref ? void 0 : meta.description,
1279
+ examples: meta.examples
1280
+ });
1281
+ if (!cyclic) return `${objectKey(name)}: ${body}`;
1282
+ const returnType = recursiveGetterType({
1283
+ ctx,
1284
+ schema,
1285
+ required
1286
+ });
1287
+ return returnType ? `get ${objectKey(name)}(): ${returnType} { return ${body} }` : lazyGetter({
1288
+ name,
1289
+ body
1290
+ });
1291
+ });
1292
+ }
1293
+ /**
1294
+ * Breaks getter inference cycles without widening recursive refs and arrays to `z.ZodType`.
1295
+ */
1296
+ function recursiveGetterType({ ctx, schema, required }) {
1297
+ function baseType(schema) {
1298
+ if (ctx.options.nodes?.[schema.type]) return void 0;
1299
+ if (schema.type === ast.schemaTypes.ref) {
1300
+ const saved = ctx.options.cyclicSchemas;
1301
+ ctx.options.cyclicSchemas = void 0;
1302
+ const name = ctx.transform(schema);
1303
+ ctx.options.cyclicSchemas = saved;
1304
+ return name ? `typeof ${name}` : void 0;
1305
+ }
1306
+ if (schema.type === ast.schemaTypes.array && schema.items?.length === 1) {
1307
+ const item = baseType(schema.items[0]);
1308
+ return item ? `z.ZodArray<${item}>` : void 0;
1309
+ }
1310
+ }
1311
+ const base = baseType(schema);
1312
+ if (!base) return void 0;
1313
+ const meta = syncSchemaRef(schema);
1314
+ const optional = schema.optional || required === false;
1315
+ const wrapped = (() => {
1316
+ if (schema.nullish || meta.nullable && optional) return `z.ZodOptional<z.ZodNullable<${base}>>`;
1317
+ if (optional) return `z.ZodOptional<${base}>`;
1318
+ if (meta.nullable) return `z.ZodNullable<${base}>`;
1319
+ return base;
1320
+ })();
1321
+ return meta.default !== void 0 && defaultLiteral(schema, meta.default) !== null ? `z.ZodDefault<${wrapped}>` : wrapped;
1171
1322
  }
1172
1323
  /**
1173
1324
  * Types the direction probe skips. They delegate to their children rather than reading
1174
1325
  * `direction` themselves, so {@link containsDirectionalNode} walks into the children instead.
1175
1326
  */
1176
1327
  const CONTAINER_TYPES = /* @__PURE__ */ new Set([
1177
- "object",
1178
- "array",
1179
- "tuple",
1180
- "union",
1181
- "intersection",
1182
- "ref"
1328
+ ast.schemaTypes.object,
1329
+ ast.schemaTypes.array,
1330
+ ast.schemaTypes.tuple,
1331
+ ast.schemaTypes.union,
1332
+ ast.schemaTypes.intersection,
1333
+ ast.schemaTypes.ref
1183
1334
  ]);
1184
1335
  /**
1185
1336
  * Runs the node's effective handler (a `printer.nodes` override, else the built-in) once per
@@ -1211,7 +1362,7 @@ function variesByDirection({ node, printerOptions }) {
1211
1362
  */
1212
1363
  function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */ new Set() }) {
1213
1364
  if (!node) return false;
1214
- if (node.type === "ref") {
1365
+ if (node.type === ast.schemaTypes.ref) {
1215
1366
  if (!node.ref) return false;
1216
1367
  const refName = extractRefName(node.ref);
1217
1368
  if (refName) {
@@ -1219,7 +1370,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1219
1370
  seen.add(refName);
1220
1371
  }
1221
1372
  const resolved = syncSchemaRef(node);
1222
- if (resolved.type === "ref") return false;
1373
+ if (resolved.type === ast.schemaTypes.ref) return false;
1223
1374
  return containsDirectionalNode({
1224
1375
  node: resolved,
1225
1376
  printerOptions,
@@ -1235,7 +1386,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1235
1386
  if ("items" in node && node.items) children.push(...node.items);
1236
1387
  if ("members" in node && node.members) children.push(...node.members);
1237
1388
  if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
1238
- const propertyNames = node.type === "object" ? node.propertyNames : void 0;
1389
+ const propertyNames = node.type === ast.schemaTypes.object ? node.propertyNames : void 0;
1239
1390
  if (propertyNames) children.push(propertyNames);
1240
1391
  return children.some((child) => containsDirectionalNode({
1241
1392
  node: child,
@@ -1247,7 +1398,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1247
1398
  * Names of the `$ref` schemas the generator should route to their input (encode) variant.
1248
1399
  */
1249
1400
  function collectDirectionalRefNames({ node, printerOptions }) {
1250
- return ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsDirectionalNode({
1401
+ return ast.collectSync(node, { schema: (n) => n.type === ast.schemaTypes.ref && n.ref && containsDirectionalNode({
1251
1402
  node: n,
1252
1403
  printerOptions
1253
1404
  }) ? ast.resolveRefName(n) ?? void 0 : void 0 });
@@ -1339,7 +1490,7 @@ const scalarNodes = {
1339
1490
  enum(node) {
1340
1491
  const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1341
1492
  if (node.namedEnumValues?.length) {
1342
- const literals = nonNullValues.map((v) => `z.literal(${formatDefault(v)})`);
1493
+ const literals = nonNullValues.map((v) => buildLiteral(v));
1343
1494
  if (literals.length === 1) return literals[0];
1344
1495
  return `z.union([${literals.join(", ")}])`;
1345
1496
  }
@@ -1360,6 +1511,7 @@ const scalarNodes = {
1360
1511
  */
1361
1512
  const printerZod = ast.createPrinter((options) => {
1362
1513
  const cyclicSchemaNames = options.cyclicSchemas;
1514
+ const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
1363
1515
  return {
1364
1516
  name: "zod",
1365
1517
  options,
@@ -1385,37 +1537,40 @@ const printerZod = ast.createPrinter((options) => {
1385
1537
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1386
1538
  const isLoose = node.additionalProperties === true;
1387
1539
  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
- })();
1540
+ return objectConstraints({
1541
+ value: (() => {
1542
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1543
+ if (node.additionalProperties && node.additionalProperties !== true) {
1544
+ const catchallType = this.transform(node.additionalProperties);
1545
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1546
+ return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1547
+ }
1548
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1549
+ const unknownType = this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1550
+ return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1551
+ }
1552
+ if (node.additionalProperties === true) return objectBase;
1553
+ if (isStrict) return objectBase;
1554
+ if (patterns.length > 0) {
1555
+ const values = patterns.map(([, valueSchema]) => {
1556
+ const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1557
+ return valueSchema.nullable ? `${valueType}.nullable()` : valueType;
1558
+ });
1559
+ const distinct = [...new Set(values)];
1560
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1561
+ if (entries.length > 0) return `${objectBase}.catchall(${value})`;
1562
+ return `z.record(${patternKeySchema({
1563
+ patterns: patterns.map(([pattern]) => pattern),
1564
+ regexType: this.options.regexType
1565
+ })}, ${value})`;
1566
+ }
1567
+ return objectBase;
1568
+ })(),
1569
+ node
1570
+ });
1416
1571
  },
1417
1572
  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({
1573
+ 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
1574
  ...node,
1420
1575
  regexType: this.options.regexType
1421
1576
  })}`;
@@ -1428,7 +1583,7 @@ const printerZod = ast.createPrinter((options) => {
1428
1583
  const nodeMembers = node.members ?? [];
1429
1584
  const isDiscriminated = !!node.discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1430
1585
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1431
- const isStrict = node.strategy === "one" && !isXor;
1586
+ const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1432
1587
  const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ schema, output }) => output && isStrict ? strictOneOfMember(output, schema, cyclicSchemaNames) : output).filter(Boolean);
1433
1588
  if (members.length === 0) return "";
1434
1589
  if (members.length === 1) return members[0];
@@ -1439,11 +1594,21 @@ const printerZod = ast.createPrinter((options) => {
1439
1594
  intersection(node) {
1440
1595
  const members = node.members ?? [];
1441
1596
  if (members.length === 0) return "";
1597
+ const hasSiblingProperties = members.some((member) => member.type === ast.schemaTypes.object && member.properties.length > 0);
1598
+ const transform = (member) => {
1599
+ if (!hasSiblingProperties) return this.transform(member);
1600
+ unionsWithSiblings.add(member);
1601
+ try {
1602
+ return this.transform(member);
1603
+ } finally {
1604
+ unionsWithSiblings.delete(member);
1605
+ }
1606
+ };
1442
1607
  const [first, ...rest] = members;
1443
1608
  if (!first) return "";
1444
- const firstBase = this.transform(first);
1609
+ const firstBase = transform(first);
1445
1610
  if (!firstBase) return "";
1446
- if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `${acc}.extend(${buildZodObjectShape(this, member)})`, firstBase);
1611
+ if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return preserveRequiredProperties(members).slice(1).reduce((acc, member) => `${acc}.extend(${buildZodObjectShape(this, member)})`, firstBase);
1447
1612
  return rest.reduce((acc, member) => {
1448
1613
  const constraint = getMemberConstraint({
1449
1614
  member,
@@ -1452,7 +1617,7 @@ const printerZod = ast.createPrinter((options) => {
1452
1617
  numbers: numberConstraints
1453
1618
  });
1454
1619
  if (constraint) return acc + constraint;
1455
- const transformed = this.transform(member);
1620
+ const transformed = transform(member);
1456
1621
  return transformed ? `${acc}.and(${transformed})` : acc;
1457
1622
  }, firstBase);
1458
1623
  }
@@ -1472,20 +1637,25 @@ const printerZod = ast.createPrinter((options) => {
1472
1637
  }
1473
1638
  };
1474
1639
  });
1475
- //#endregion
1476
- //#region src/printers/printerZodMini.ts
1640
+ // src/printers/printerZodMini.ts
1477
1641
  /**
1478
1642
  * Builds the `{ key: value, … }` shape for an object node with the functional `zod/mini` modifiers.
1479
1643
  */
1480
1644
  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
- }));
1645
+ return buildObjectShape(ctx, node, ({ name, value, schema, required, meta, cyclic }) => {
1646
+ const body = applyMiniModifiers({
1647
+ value,
1648
+ schema,
1649
+ nullable: meta.nullable,
1650
+ optional: schema.optional || required === false,
1651
+ nullish: schema.nullish,
1652
+ defaultValue: meta.default
1653
+ });
1654
+ return cyclic ? lazyGetter({
1655
+ name,
1656
+ body
1657
+ }) : `${objectKey(name)}: ${body}`;
1658
+ });
1489
1659
  }
1490
1660
  /**
1491
1661
  * Zod v4 Mini printer built with `definePrinter`.
@@ -1501,6 +1671,7 @@ function buildZodMiniObjectShape(ctx, node) {
1501
1671
  */
1502
1672
  const printerZodMini = ast.createPrinter((options) => {
1503
1673
  const cyclicSchemaNames = options.cyclicSchemas;
1674
+ const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
1504
1675
  return {
1505
1676
  name: "zod-mini",
1506
1677
  options,
@@ -1573,7 +1744,7 @@ const printerZodMini = ast.createPrinter((options) => {
1573
1744
  enum(node) {
1574
1745
  const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1575
1746
  if (node.namedEnumValues?.length) {
1576
- const literals = nonNullValues.map((v) => `z.literal(${formatDefault(v)})`);
1747
+ const literals = nonNullValues.map((v) => buildLiteral(v));
1577
1748
  if (literals.length === 1) return literals[0];
1578
1749
  return `z.union([${literals.join(", ")}])`;
1579
1750
  }
@@ -1595,32 +1766,40 @@ const printerZodMini = ast.createPrinter((options) => {
1595
1766
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1596
1767
  const isLoose = node.additionalProperties === true;
1597
1768
  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;
1769
+ return objectConstraints({
1770
+ value: (() => {
1771
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1772
+ if (node.additionalProperties && node.additionalProperties !== true) {
1773
+ const catchallType = this.transform(node.additionalProperties);
1774
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1775
+ return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1776
+ }
1777
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1778
+ const unknownType = this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1779
+ return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1780
+ }
1781
+ if (node.additionalProperties === true) return objectBase;
1782
+ if (isStrict) return objectBase;
1783
+ if (patterns.length > 0) {
1784
+ const values = patterns.map(([, valueSchema]) => {
1785
+ const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1786
+ return valueSchema.nullable ? `z.nullable(${valueType})` : valueType;
1787
+ });
1788
+ const distinct = [...new Set(values)];
1789
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1790
+ if (entries.length > 0) return `z.catchall(${objectBase}, ${value})`;
1791
+ return `z.record(${patternKeySchemaMini({
1792
+ patterns: patterns.map(([pattern]) => pattern),
1793
+ regexType: this.options.regexType
1794
+ })}, ${value})`;
1795
+ }
1796
+ return objectBase;
1797
+ })(),
1798
+ node
1799
+ });
1621
1800
  },
1622
1801
  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({
1802
+ 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
1803
  ...node,
1625
1804
  regexType: this.options.regexType
1626
1805
  })}`;
@@ -1633,7 +1812,7 @@ const printerZodMini = ast.createPrinter((options) => {
1633
1812
  const nodeMembers = node.members ?? [];
1634
1813
  const isDiscriminated = !!node.discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1635
1814
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1636
- const isStrict = node.strategy === "one" && !isXor;
1815
+ const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1637
1816
  const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ output }) => output).filter(Boolean);
1638
1817
  if (members.length === 0) return "";
1639
1818
  if (members.length === 1) return members[0];
@@ -1644,11 +1823,21 @@ const printerZodMini = ast.createPrinter((options) => {
1644
1823
  intersection(node) {
1645
1824
  const members = node.members ?? [];
1646
1825
  if (members.length === 0) return "";
1826
+ const hasSiblingProperties = members.some((member) => member.type === ast.schemaTypes.object && member.properties.length > 0);
1827
+ const transform = (member) => {
1828
+ if (!hasSiblingProperties) return this.transform(member);
1829
+ unionsWithSiblings.add(member);
1830
+ try {
1831
+ return this.transform(member);
1832
+ } finally {
1833
+ unionsWithSiblings.delete(member);
1834
+ }
1835
+ };
1647
1836
  const [first, ...rest] = members;
1648
1837
  if (!first) return "";
1649
- const firstBase = this.transform(first);
1838
+ const firstBase = transform(first);
1650
1839
  if (!firstBase) return "";
1651
- if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `z.extend(${acc}, ${buildZodMiniObjectShape(this, member)})`, firstBase);
1840
+ if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return preserveRequiredProperties(members).slice(1).reduce((acc, member) => `z.extend(${acc}, ${buildZodMiniObjectShape(this, member)})`, firstBase);
1652
1841
  return rest.reduce((acc, member) => {
1653
1842
  const constraint = getMemberConstraint({
1654
1843
  member,
@@ -1657,7 +1846,7 @@ const printerZodMini = ast.createPrinter((options) => {
1657
1846
  numbers: numberChecksMini
1658
1847
  });
1659
1848
  if (constraint) return acc + constraint;
1660
- const transformed = this.transform(member);
1849
+ const transformed = transform(member);
1661
1850
  return transformed ? `z.intersection(${acc}, ${transformed})` : acc;
1662
1851
  }, firstBase);
1663
1852
  }
@@ -1675,8 +1864,7 @@ const printerZodMini = ast.createPrinter((options) => {
1675
1864
  }
1676
1865
  };
1677
1866
  });
1678
- //#endregion
1679
- //#region src/generators/zodGenerator.tsx
1867
+ // src/generators/zodGenerator.tsx
1680
1868
  /**
1681
1869
  * The helper an inferred alias is printed with. `'codec'` reads a request (encode) schema from its
1682
1870
  * input and a response (decode) schema from its output; `true` keeps `z.infer` for both.
@@ -2137,17 +2325,14 @@ const zodGenerator = defineGenerator({
2137
2325
  });
2138
2326
  }
2139
2327
  });
2140
- //#endregion
2141
- //#region kubb-extension.json
2328
+ // kubb-extension.json
2142
2329
  var compatibility = {
2143
- "kubb": ">=5.8.3",
2330
+ "kubb": ">=5.14.6",
2144
2331
  "node": ">=22.22"
2145
2332
  };
2146
- //#endregion
2147
- //#region package.json
2148
- var version = "5.7.2";
2149
- //#endregion
2150
- //#region src/resolvers/resolverZod.ts
2333
+ // package.json
2334
+ var version = "5.7.4";
2335
+ // src/resolvers/resolverZod.ts
2151
2336
  /**
2152
2337
  * Default resolver used by `@kubb/plugin-zod`. Decides the names and file
2153
2338
  * paths for every generated Zod schema. Schemas use camelCase with a
@@ -2166,15 +2351,15 @@ var version = "5.7.2";
2166
2351
  const resolverZod = createResolver({
2167
2352
  pluginName: "plugin-zod",
2168
2353
  name(name) {
2169
- return ensureValidVarName(camelCase(name, { suffix: "schema" }));
2354
+ return this.memo(`name:${name}`, () => ensureValidVarName(camelCase(name, { suffix: "schema" })));
2170
2355
  },
2171
2356
  file: createCasedFile((part) => camelCase(part, { suffix: "schema" })),
2172
2357
  schema: {
2173
2358
  typeName(name) {
2174
- return ensureValidVarName(pascalCase(name, { suffix: "schema type" }));
2359
+ return this.memo(`typeName:${name}`, () => ensureValidVarName(pascalCase(name, { suffix: "schema type" })));
2175
2360
  },
2176
2361
  type(name) {
2177
- return ensureValidVarName(pascalCase(name, { suffix: "type" }));
2362
+ return this.memo(`type:${name}`, () => ensureValidVarName(pascalCase(name, { suffix: "type" })));
2178
2363
  },
2179
2364
  inputName(name) {
2180
2365
  return this.name(`${name} input`);
@@ -2193,15 +2378,22 @@ const resolverZod = createResolver({
2193
2378
  response: {
2194
2379
  ...createOperationResponseResolver(),
2195
2380
  error(node) {
2196
- return this.name(`${node.operationId} Error`);
2381
+ const name = this.name(`${node.operationId} Error`);
2382
+ return this.resolveName({
2383
+ key: operationKey(node, "response", "error"),
2384
+ name
2385
+ });
2197
2386
  },
2198
2387
  options(node) {
2199
- return this.schema.type(this.name(`${node.operationId} Options`));
2388
+ const name = this.schema.type(this.name(`${node.operationId} Options`));
2389
+ return this.resolveName({
2390
+ key: operationKey(node, "response", "options"),
2391
+ name
2392
+ });
2200
2393
  }
2201
2394
  }
2202
2395
  });
2203
- //#endregion
2204
- //#region src/plugin.ts
2396
+ // src/plugin.ts
2205
2397
  /**
2206
2398
  * Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
2207
2399
  * cross-plugin dependency references.
@@ -2267,7 +2459,6 @@ const pluginZod = defineExtension({
2267
2459
  };
2268
2460
  }
2269
2461
  });
2270
- //#endregion
2271
2462
  export { pluginZod as default, pluginZod, pluginZodName, printerZod, printerZodMini, resolverZod, zodGenerator };
2272
2463
 
2273
2464
  //# sourceMappingURL=index.js.map