@schemashift/zod-v3-v4 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -8,12 +8,25 @@ declare function createZodV3ToV4Handler(): TransformHandler;
8
8
  *
9
9
  * Key changes in Zod v4:
10
10
  * 1. z.record() now requires explicit key type
11
- * 2. .default() has stricter type inference
11
+ * 2. .default() has stricter type inference (matches output type, not input)
12
12
  * 3. error.errors renamed to error.issues
13
- * 4. .uuid() has stricter validation (RFC 4122 compliant)
14
- * 5. z.function() input/output changed
13
+ * 4. .uuid() has stricter validation (RFC 9562/4122 compliant)
14
+ * 5. z.function() completely overhauled — .args()/.returns() removed
15
15
  * 6. Branded types use different syntax
16
16
  * 7. z.discriminatedUnion() requires literal discriminator
17
+ * 8. String format methods moved to top-level (e.g., .email() → z.email())
18
+ * 9. .deepPartial() removed (was deprecated anti-pattern)
19
+ * 10. z.promise() deprecated (await values before parsing)
20
+ * 11. .strip() deprecated (default behavior)
21
+ * 12. .nonempty() type inference changed (no longer tuple type)
22
+ * 13. z.coerce.* input type changed to unknown
23
+ * 14. z.partialRecord() added for optional key behavior
24
+ * 15. z.guid() added for lenient UUID matching
25
+ * 16. z.iso.* validators for date/time/datetime/duration
26
+ * 17. .prefault() added (restores v3 .default() parse behavior)
27
+ * 18. .addIssue()/.addIssues() deprecated in favor of issues.push()
28
+ * 19. ZodError precedence reversal: schema-level error maps override parse-time
29
+ * 20. z.ostring()/z.onumber() etc. removed (optional shorthands)
17
30
  */
18
31
  declare const V3_TO_V4_PATTERNS: {
19
32
  recordSingleArg: RegExp;
@@ -111,6 +124,50 @@ declare class ZodV3ToV4Transformer {
111
124
  * Detect imports of utility types that moved to 'zod/v4/core' in v4.
112
125
  */
113
126
  private checkUtilityTypeImports;
127
+ /**
128
+ * Warn about string format methods that moved to top-level functions in v4.
129
+ * e.g., z.string().email() → z.email() (instance methods are deprecated but still work)
130
+ */
131
+ private checkStringFormatMethods;
132
+ /**
133
+ * Check for deprecated factory functions (z.promise(), z.ostring(), etc.)
134
+ */
135
+ private checkDeprecatedFactories;
136
+ /**
137
+ * Transform z.function().args().returns() to z.function({ input, output }) in v4.
138
+ */
139
+ private transformFunctionValidation;
140
+ /**
141
+ * Transform z.ostring(), z.onumber(), z.oboolean() to explicit .optional() calls.
142
+ */
143
+ private transformOptionalShorthands;
144
+ /**
145
+ * Check for .nonempty() which has a type inference change in v4.
146
+ * v3: [string, ...string[]] (tuple with rest)
147
+ * v4: string[] (matches .min(1))
148
+ */
149
+ private checkNonemptyTypeChange;
150
+ /**
151
+ * Check for z.coerce.* usage — input type changed to unknown in v4.
152
+ */
153
+ private checkCoerceInputType;
154
+ /**
155
+ * Check for .deepPartial() which was removed entirely in v4.
156
+ */
157
+ private checkDeepPartialRemoval;
158
+ /**
159
+ * Check for .strip() which is deprecated in v4 (it was the default behavior).
160
+ */
161
+ private checkStripDeprecation;
162
+ /**
163
+ * Check for errorMap usage patterns where precedence changed in v4.
164
+ * In v4, schema-level error maps take precedence over parse-time error maps (inverted from v3).
165
+ */
166
+ private checkErrorMapPrecedence;
167
+ /**
168
+ * Check for ctx.addIssue() / ctx.addIssues() which are deprecated in v4.
169
+ */
170
+ private checkAddIssueMethods;
114
171
  }
115
172
 
116
173
  export { BEHAVIOR_CHANGES, V3_TO_V4_PATTERNS, ZodV3ToV4Transformer, createZodV3ToV4Handler };
package/dist/index.d.ts CHANGED
@@ -8,12 +8,25 @@ declare function createZodV3ToV4Handler(): TransformHandler;
8
8
  *
9
9
  * Key changes in Zod v4:
10
10
  * 1. z.record() now requires explicit key type
11
- * 2. .default() has stricter type inference
11
+ * 2. .default() has stricter type inference (matches output type, not input)
12
12
  * 3. error.errors renamed to error.issues
13
- * 4. .uuid() has stricter validation (RFC 4122 compliant)
14
- * 5. z.function() input/output changed
13
+ * 4. .uuid() has stricter validation (RFC 9562/4122 compliant)
14
+ * 5. z.function() completely overhauled — .args()/.returns() removed
15
15
  * 6. Branded types use different syntax
16
16
  * 7. z.discriminatedUnion() requires literal discriminator
17
+ * 8. String format methods moved to top-level (e.g., .email() → z.email())
18
+ * 9. .deepPartial() removed (was deprecated anti-pattern)
19
+ * 10. z.promise() deprecated (await values before parsing)
20
+ * 11. .strip() deprecated (default behavior)
21
+ * 12. .nonempty() type inference changed (no longer tuple type)
22
+ * 13. z.coerce.* input type changed to unknown
23
+ * 14. z.partialRecord() added for optional key behavior
24
+ * 15. z.guid() added for lenient UUID matching
25
+ * 16. z.iso.* validators for date/time/datetime/duration
26
+ * 17. .prefault() added (restores v3 .default() parse behavior)
27
+ * 18. .addIssue()/.addIssues() deprecated in favor of issues.push()
28
+ * 19. ZodError precedence reversal: schema-level error maps override parse-time
29
+ * 20. z.ostring()/z.onumber() etc. removed (optional shorthands)
17
30
  */
18
31
  declare const V3_TO_V4_PATTERNS: {
19
32
  recordSingleArg: RegExp;
@@ -111,6 +124,50 @@ declare class ZodV3ToV4Transformer {
111
124
  * Detect imports of utility types that moved to 'zod/v4/core' in v4.
112
125
  */
113
126
  private checkUtilityTypeImports;
127
+ /**
128
+ * Warn about string format methods that moved to top-level functions in v4.
129
+ * e.g., z.string().email() → z.email() (instance methods are deprecated but still work)
130
+ */
131
+ private checkStringFormatMethods;
132
+ /**
133
+ * Check for deprecated factory functions (z.promise(), z.ostring(), etc.)
134
+ */
135
+ private checkDeprecatedFactories;
136
+ /**
137
+ * Transform z.function().args().returns() to z.function({ input, output }) in v4.
138
+ */
139
+ private transformFunctionValidation;
140
+ /**
141
+ * Transform z.ostring(), z.onumber(), z.oboolean() to explicit .optional() calls.
142
+ */
143
+ private transformOptionalShorthands;
144
+ /**
145
+ * Check for .nonempty() which has a type inference change in v4.
146
+ * v3: [string, ...string[]] (tuple with rest)
147
+ * v4: string[] (matches .min(1))
148
+ */
149
+ private checkNonemptyTypeChange;
150
+ /**
151
+ * Check for z.coerce.* usage — input type changed to unknown in v4.
152
+ */
153
+ private checkCoerceInputType;
154
+ /**
155
+ * Check for .deepPartial() which was removed entirely in v4.
156
+ */
157
+ private checkDeepPartialRemoval;
158
+ /**
159
+ * Check for .strip() which is deprecated in v4 (it was the default behavior).
160
+ */
161
+ private checkStripDeprecation;
162
+ /**
163
+ * Check for errorMap usage patterns where precedence changed in v4.
164
+ * In v4, schema-level error maps take precedence over parse-time error maps (inverted from v3).
165
+ */
166
+ private checkErrorMapPrecedence;
167
+ /**
168
+ * Check for ctx.addIssue() / ctx.addIssues() which are deprecated in v4.
169
+ */
170
+ private checkAddIssueMethods;
114
171
  }
115
172
 
116
173
  export { BEHAVIOR_CHANGES, V3_TO_V4_PATTERNS, ZodV3ToV4Transformer, createZodV3ToV4Handler };
package/dist/index.js CHANGED
@@ -12,19 +12,23 @@ var V3_TO_V4_PATTERNS = {
12
12
  };
13
13
  var BEHAVIOR_CHANGES = /* @__PURE__ */ new Set([
14
14
  "default",
15
- // Type inference changed
15
+ // Type inference changed; now matches output type, use .prefault() for v3 behavior
16
16
  "uuid",
17
- // Stricter validation
17
+ // Stricter RFC 9562/4122 validation; use z.guid() for lenient matching
18
18
  "record",
19
19
  // Requires key type
20
20
  "discriminatedUnion",
21
21
  // Stricter discriminator requirements
22
22
  "function",
23
- // Input/output parameter changes
23
+ // Complete overhaul — .args()/.returns() removed
24
24
  "pipe",
25
25
  // Stricter type checking in v4
26
- "catch"
26
+ "catch",
27
27
  // .catch() on optional properties always returns catch value in v4
28
+ "nonempty",
29
+ // Type inference changed: string[] instead of [string, ...string[]]
30
+ "coerce"
31
+ // Input type changed to unknown for all z.coerce.* schemas
28
32
  ]);
29
33
  var DEPRECATED_METHODS = /* @__PURE__ */ new Set([
30
34
  "merge",
@@ -33,8 +37,26 @@ var DEPRECATED_METHODS = /* @__PURE__ */ new Set([
33
37
  // Use .check() instead
34
38
  "strict",
35
39
  // Use z.strictObject() instead
36
- "passthrough"
40
+ "passthrough",
37
41
  // Use z.looseObject() instead
42
+ "deepPartial",
43
+ // Removed entirely — was deprecated anti-pattern
44
+ "strip",
45
+ // Deprecated — default behavior in v4
46
+ "nonstrict"
47
+ // Removed entirely
48
+ ]);
49
+ var DEPRECATED_FACTORIES = /* @__PURE__ */ new Set([
50
+ "promise",
51
+ // z.promise() deprecated — await values before parsing
52
+ "ostring",
53
+ // z.ostring() removed (optional shorthand)
54
+ "onumber",
55
+ // z.onumber() removed
56
+ "oboolean",
57
+ // z.oboolean() removed
58
+ "preprocess"
59
+ // z.preprocess() removed — use z.pipe() instead
38
60
  ]);
39
61
  var AUTO_TRANSFORMABLE = /* @__PURE__ */ new Set([
40
62
  "superRefine",
@@ -51,6 +73,22 @@ var UTILITY_TYPES = /* @__PURE__ */ new Set([
51
73
  "ZodTypeAny",
52
74
  "ZodFirstPartyTypeKind"
53
75
  ]);
76
+ var STRING_FORMAT_TO_TOPLEVEL = /* @__PURE__ */ new Map([
77
+ ["email", "z.email()"],
78
+ ["url", "z.url()"],
79
+ ["uuid", "z.uuid()"],
80
+ ["emoji", "z.emoji()"],
81
+ ["nanoid", "z.nanoid()"],
82
+ ["cuid", "z.cuid()"],
83
+ ["cuid2", "z.cuid2()"],
84
+ ["ulid", "z.ulid()"],
85
+ ["ipv4", "z.ipv4()"],
86
+ ["ipv6", "z.ipv6()"],
87
+ ["cidrv4", "z.cidrv4()"],
88
+ ["cidrv6", "z.cidrv6()"],
89
+ ["base64", "z.base64()"],
90
+ ["base64url", "z.base64url()"]
91
+ ]);
54
92
 
55
93
  // src/transformer.ts
56
94
  var ZodV3ToV4Transformer = class {
@@ -71,13 +109,23 @@ var ZodV3ToV4Transformer = class {
71
109
  this.transformFlatten(sourceFile);
72
110
  this.transformFormat(sourceFile);
73
111
  this.transformDefAccess(sourceFile);
112
+ this.transformFunctionValidation(sourceFile);
113
+ this.transformOptionalShorthands(sourceFile);
74
114
  this.checkDeprecatedMethods(sourceFile);
115
+ this.checkDeprecatedFactories(sourceFile);
116
+ this.checkStringFormatMethods(sourceFile);
75
117
  this.checkBehaviorChanges(sourceFile);
76
118
  this.checkInstanceofError(sourceFile);
77
119
  this.checkTransformRefineOrder(sourceFile);
78
120
  this.checkDefaultOptional(sourceFile);
79
121
  this.checkCatchOptional(sourceFile);
80
122
  this.checkUtilityTypeImports(sourceFile);
123
+ this.checkNonemptyTypeChange(sourceFile);
124
+ this.checkCoerceInputType(sourceFile);
125
+ this.checkDeepPartialRemoval(sourceFile);
126
+ this.checkStripDeprecation(sourceFile);
127
+ this.checkErrorMapPrecedence(sourceFile);
128
+ this.checkAddIssueMethods(sourceFile);
81
129
  return {
82
130
  success: this.errors.length === 0,
83
131
  filePath,
@@ -605,6 +653,238 @@ var ZodV3ToV4Transformer = class {
605
653
  }
606
654
  }
607
655
  }
656
+ /**
657
+ * Warn about string format methods that moved to top-level functions in v4.
658
+ * e.g., z.string().email() → z.email() (instance methods are deprecated but still work)
659
+ */
660
+ checkStringFormatMethods(sourceFile) {
661
+ const filePath = sourceFile.getFilePath();
662
+ sourceFile.forEachDescendant((node) => {
663
+ if (Node.isCallExpression(node)) {
664
+ const expression = node.getExpression();
665
+ if (Node.isPropertyAccessExpression(expression)) {
666
+ const name = expression.getName();
667
+ if (STRING_FORMAT_TO_TOPLEVEL.has(name)) {
668
+ const chainText = node.getText();
669
+ if (chainText.includes("z.string()") || chainText.includes(".string()")) {
670
+ const topLevel = STRING_FORMAT_TO_TOPLEVEL.get(name);
671
+ const lineNumber = node.getStartLineNumber();
672
+ this.warnings.push(
673
+ `${filePath}:${lineNumber}: .${name}() is deprecated as an instance method in v4. Use ${topLevel} as a top-level function instead. The instance method still works but may be removed in future versions.`
674
+ );
675
+ }
676
+ }
677
+ }
678
+ }
679
+ });
680
+ }
681
+ /**
682
+ * Check for deprecated factory functions (z.promise(), z.ostring(), etc.)
683
+ */
684
+ checkDeprecatedFactories(sourceFile) {
685
+ const filePath = sourceFile.getFilePath();
686
+ sourceFile.forEachDescendant((node) => {
687
+ if (Node.isCallExpression(node)) {
688
+ const expression = node.getExpression();
689
+ if (Node.isPropertyAccessExpression(expression)) {
690
+ const name = expression.getName();
691
+ const object = expression.getExpression();
692
+ if (object.getText() === "z" && DEPRECATED_FACTORIES.has(name)) {
693
+ const lineNumber = node.getStartLineNumber();
694
+ switch (name) {
695
+ case "promise":
696
+ this.warnings.push(
697
+ `${filePath}:${lineNumber}: z.promise() is deprecated in v4. Await values before parsing instead of wrapping in z.promise().`
698
+ );
699
+ break;
700
+ case "ostring":
701
+ this.warnings.push(
702
+ `${filePath}:${lineNumber}: z.ostring() is removed in v4. Use z.string().optional() instead.`
703
+ );
704
+ break;
705
+ case "onumber":
706
+ this.warnings.push(
707
+ `${filePath}:${lineNumber}: z.onumber() is removed in v4. Use z.number().optional() instead.`
708
+ );
709
+ break;
710
+ case "oboolean":
711
+ this.warnings.push(
712
+ `${filePath}:${lineNumber}: z.oboolean() is removed in v4. Use z.boolean().optional() instead.`
713
+ );
714
+ break;
715
+ case "preprocess":
716
+ this.warnings.push(
717
+ `${filePath}:${lineNumber}: z.preprocess() is removed in v4. Use z.pipe(z.unknown(), z.transform(fn), targetSchema) instead.`
718
+ );
719
+ break;
720
+ }
721
+ }
722
+ }
723
+ }
724
+ });
725
+ }
726
+ /**
727
+ * Transform z.function().args().returns() to z.function({ input, output }) in v4.
728
+ */
729
+ transformFunctionValidation(sourceFile) {
730
+ const filePath = sourceFile.getFilePath();
731
+ sourceFile.forEachDescendant((node) => {
732
+ if (Node.isCallExpression(node)) {
733
+ const expression = node.getExpression();
734
+ if (Node.isPropertyAccessExpression(expression)) {
735
+ const name = expression.getName();
736
+ if (name === "args" || name === "returns") {
737
+ const chainText = node.getText();
738
+ if (chainText.includes("z.function()") && (chainText.includes(".args(") || chainText.includes(".returns("))) {
739
+ const lineNumber = node.getStartLineNumber();
740
+ this.warnings.push(
741
+ `${filePath}:${lineNumber}: z.function().args().returns() is removed in v4. Use z.function({ input: z.tuple([...]), output: schema }) instead. The result is no longer a Zod schema \u2014 use .implement() to wrap functions.`
742
+ );
743
+ }
744
+ }
745
+ }
746
+ }
747
+ });
748
+ }
749
+ /**
750
+ * Transform z.ostring(), z.onumber(), z.oboolean() to explicit .optional() calls.
751
+ */
752
+ transformOptionalShorthands(sourceFile) {
753
+ const filePath = sourceFile.getFilePath();
754
+ const fullText = sourceFile.getFullText();
755
+ const replacements = [
756
+ { pattern: /\bz\.ostring\(\)/g, replacement: "z.string().optional()", name: "z.ostring()" },
757
+ { pattern: /\bz\.onumber\(\)/g, replacement: "z.number().optional()", name: "z.onumber()" },
758
+ {
759
+ pattern: /\bz\.oboolean\(\)/g,
760
+ replacement: "z.boolean().optional()",
761
+ name: "z.oboolean()"
762
+ }
763
+ ];
764
+ let newText = fullText;
765
+ for (const { pattern, replacement, name } of replacements) {
766
+ if (pattern.test(newText)) {
767
+ newText = newText.replace(pattern, replacement);
768
+ this.warnings.push(
769
+ `${filePath}: ${name} auto-transformed to ${replacement}. Removed in v4.`
770
+ );
771
+ }
772
+ }
773
+ if (newText !== fullText) {
774
+ sourceFile.replaceWithText(newText);
775
+ }
776
+ }
777
+ /**
778
+ * Check for .nonempty() which has a type inference change in v4.
779
+ * v3: [string, ...string[]] (tuple with rest)
780
+ * v4: string[] (matches .min(1))
781
+ */
782
+ checkNonemptyTypeChange(sourceFile) {
783
+ const filePath = sourceFile.getFilePath();
784
+ sourceFile.forEachDescendant((node) => {
785
+ if (Node.isCallExpression(node)) {
786
+ const expression = node.getExpression();
787
+ if (Node.isPropertyAccessExpression(expression) && expression.getName() === "nonempty") {
788
+ const lineNumber = node.getStartLineNumber();
789
+ this.warnings.push(
790
+ `${filePath}:${lineNumber}: .nonempty() type inference changed in v4. v3 inferred [T, ...T[]] (tuple with rest), v4 infers T[] (same as .min(1)). If you rely on the tuple type, use z.tuple([z.string()], z.string()) instead.`
791
+ );
792
+ }
793
+ }
794
+ });
795
+ }
796
+ /**
797
+ * Check for z.coerce.* usage — input type changed to unknown in v4.
798
+ */
799
+ checkCoerceInputType(sourceFile) {
800
+ const filePath = sourceFile.getFilePath();
801
+ const fullText = sourceFile.getFullText();
802
+ if (fullText.includes("z.coerce.")) {
803
+ sourceFile.forEachDescendant((node) => {
804
+ if (Node.isPropertyAccessExpression(node)) {
805
+ const text = node.getText();
806
+ if (text.startsWith("z.coerce.")) {
807
+ const lineNumber = node.getStartLineNumber();
808
+ this.warnings.push(
809
+ `${filePath}:${lineNumber}: z.coerce.* input type changed to 'unknown' in v4. Previously, z.coerce.string() had input type string. This enables more accurate type inference but may affect type guards.`
810
+ );
811
+ }
812
+ }
813
+ });
814
+ }
815
+ }
816
+ /**
817
+ * Check for .deepPartial() which was removed entirely in v4.
818
+ */
819
+ checkDeepPartialRemoval(sourceFile) {
820
+ const filePath = sourceFile.getFilePath();
821
+ sourceFile.forEachDescendant((node) => {
822
+ if (Node.isCallExpression(node)) {
823
+ const expression = node.getExpression();
824
+ if (Node.isPropertyAccessExpression(expression) && expression.getName() === "deepPartial") {
825
+ const lineNumber = node.getStartLineNumber();
826
+ this.warnings.push(
827
+ `${filePath}:${lineNumber}: .deepPartial() is removed in v4. This was a long-deprecated anti-pattern. Use recursive partial types or restructure schemas to avoid deep partial requirements.`
828
+ );
829
+ }
830
+ }
831
+ });
832
+ }
833
+ /**
834
+ * Check for .strip() which is deprecated in v4 (it was the default behavior).
835
+ */
836
+ checkStripDeprecation(sourceFile) {
837
+ const filePath = sourceFile.getFilePath();
838
+ sourceFile.forEachDescendant((node) => {
839
+ if (Node.isCallExpression(node)) {
840
+ const expression = node.getExpression();
841
+ if (Node.isPropertyAccessExpression(expression) && expression.getName() === "strip") {
842
+ const lineNumber = node.getStartLineNumber();
843
+ this.warnings.push(
844
+ `${filePath}:${lineNumber}: .strip() is deprecated in v4 (it was the default behavior). Remove .strip() \u2014 z.object() strips unknown keys by default. To convert a strict object back to stripping, use z.object(schema.shape).`
845
+ );
846
+ }
847
+ }
848
+ });
849
+ }
850
+ /**
851
+ * Check for errorMap usage patterns where precedence changed in v4.
852
+ * In v4, schema-level error maps take precedence over parse-time error maps (inverted from v3).
853
+ */
854
+ checkErrorMapPrecedence(sourceFile) {
855
+ const filePath = sourceFile.getFilePath();
856
+ const fullText = sourceFile.getFullText();
857
+ const hasSchemaErrorMap = fullText.includes("errorMap:") || fullText.includes("error:");
858
+ const hasParseErrorMap = fullText.includes(".parse(") && fullText.includes("errorMap") || fullText.includes(".safeParse(") && fullText.includes("errorMap");
859
+ if (hasSchemaErrorMap && hasParseErrorMap) {
860
+ this.warnings.push(
861
+ `${filePath}: Error map precedence changed in v4. Schema-level error maps now take precedence over parse-time error maps (inverted from v3). Review error map usage to ensure correct error messages.`
862
+ );
863
+ }
864
+ }
865
+ /**
866
+ * Check for ctx.addIssue() / ctx.addIssues() which are deprecated in v4.
867
+ */
868
+ checkAddIssueMethods(sourceFile) {
869
+ const filePath = sourceFile.getFilePath();
870
+ const fullText = sourceFile.getFullText();
871
+ if (fullText.includes(".addIssue(") || fullText.includes(".addIssues(")) {
872
+ sourceFile.forEachDescendant((node) => {
873
+ if (Node.isCallExpression(node)) {
874
+ const expression = node.getExpression();
875
+ if (Node.isPropertyAccessExpression(expression)) {
876
+ const name = expression.getName();
877
+ if (name === "addIssue" || name === "addIssues") {
878
+ const lineNumber = node.getStartLineNumber();
879
+ this.warnings.push(
880
+ `${filePath}:${lineNumber}: .${name}() is deprecated in v4. Directly manipulate the issues array instead: ctx.issues.push({...}). Note: .superRefine() callbacks using addIssue should migrate to .check() with issues.push().`
881
+ );
882
+ }
883
+ }
884
+ }
885
+ });
886
+ }
887
+ }
608
888
  };
609
889
 
610
890
  // src/handler.ts