Repository: colinhacks/zod
Pull request #4224 (2025-04-18 06:50:06)

v4: Correcting the default numeric range to be inclusive
The default range (min/max representable numbers) is inclusive, not exclusive.

Addressing the bug I found and previously described in https://github.com/colinhacks/zod/pull/4074#discussion_r2048393003

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
  - Updated numeric type schemas to use inclusive bounds (`maximum` and `minimum`) instead of exclusive bounds in JSON Schema output.

- **Tests**
  - Updated test snapshots to reflect the use of inclusive numeric boundaries in schema conversion.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=colinhacks/zod&utm_content=4224):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
RobinTail: @coderabbitai review
coderabbitai[bot]: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
RobinTail: @colinhacks , I believe it's an important fix.

Patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 49e39aaa..797fff7d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -15,9 +15,6 @@
     "**/dist": true,
     "**/lib": true
   },
-  "search.exclude": {
-    "./scratch": false
-  },
   "search.useIgnoreFiles": false,
   "editor.guides.indentation": false
 }
diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts
index 1c7d647c..8ce68fcb 100644
--- a/packages/core/src/api.ts
+++ b/packages/core/src/api.ts
@@ -1609,6 +1609,7 @@ export function _stringbool(
           def: {
             check: "custom",
           },
+          onattach: [],
         },
       },
     ],
diff --git a/packages/core/src/checks.ts b/packages/core/src/checks.ts
index 18e25f02..34378c23 100644
--- a/packages/core/src/checks.ts
+++ b/packages/core/src/checks.ts
@@ -22,7 +22,7 @@ export interface $ZodCheckInternals<T> {
   // "_check"(input: $ZodResult<T>): util.MaybeAsync<void>;
   check(payload: schemas.ParsePayload<T>): util.MaybeAsync<void>;
   // _parseB(payload: ParsePayload<any>, ctx: ParseContext): util.MaybeAsync<ParsePayload>;
-  onattach?(schema: schemas.$ZodType): void;
+  onattach: ((schema: schemas.$ZodType) => void)[];
   // "_async": boolean;
   when?: ((payload: schemas.ParsePayload) => boolean) | undefined;
 }
@@ -36,6 +36,7 @@ export const $ZodCheck: core.$constructor<$ZodCheck<any>> = /*@__PURE__*/ core.$
   (inst, def) => {
     inst._zod ??= {} as any;
     inst._zod.def = def;
+    inst._zod.onattach ??= [];
   }
 );
 
@@ -68,13 +69,13 @@ export const $ZodCheckLessThan: core.$constructor<$ZodCheckLessThan> = /*@__PURE
     $ZodCheck.init(inst, def);
     const origin = numericOriginMap[typeof def.value as "number" | "bigint" | "object"];
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       const curr = inst._zod.computed.maximum ?? Number.POSITIVE_INFINITY;
       if (def.value < curr) {
         inst._zod.computed.maximum = def.value;
         inst._zod.computed.inclusive = def.inclusive;
       }
-    };
+    });
 
     inst._zod.check = (payload) => {
       if (def.inclusive ? payload.value <= def.value : payload.value < def.value) {
@@ -118,13 +119,13 @@ export const $ZodCheckGreaterThan: core.$constructor<$ZodCheckGreaterThan> = /*@
     $ZodCheck.init(inst, def);
     const origin = numericOriginMap[typeof def.value as "number" | "bigint" | "object"];
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       const curr = inst._zod.computed.minimum ?? Number.NEGATIVE_INFINITY;
       if (def.value > curr) {
         inst._zod.computed.minimum = def.value;
         inst._zod.computed.inclusive = def.inclusive;
       }
-    };
+    });
 
     inst._zod.check = (payload) => {
       if (def.inclusive ? payload.value >= def.value : payload.value > def.value) {
@@ -168,9 +169,9 @@ export const $ZodCheckMultipleOf: core.$constructor<$ZodCheckMultipleOf<number |
   /*@__PURE__*/ core.$constructor("$ZodCheckMultipleOf", (inst, def) => {
     $ZodCheck.init(inst, def);
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.multipleOf ??= def.value;
-    };
+    });
 
     inst._zod.check = (payload) => {
       if (typeof payload.value !== typeof def.value)
@@ -267,12 +268,13 @@ export const $ZodCheckNumberFormat: core.$constructor<$ZodCheckNumberFormat> = /
     const origin = isInt ? "int" : "number";
     const [minimum, maximum] = util.NUMBER_FORMAT_RANGES[def.format!];
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.format = def.format;
       inst._zod.computed.minimum = minimum;
       inst._zod.computed.maximum = maximum;
+      inst._zod.computed.inclusive = true;
       if (isInt) inst._zod.computed.pattern = regexes.integer;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -380,11 +382,11 @@ export const $ZodCheckBigIntFormat: core.$constructor<$ZodCheckBigIntFormat> = /
 
     const [minimum, maximum] = util.BIGINT_FORMAT_RANGES[def.format!];
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.format = def.format;
       inst._zod.computed.minimum = minimum;
       inst._zod.computed.maximum = maximum;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -441,10 +443,10 @@ export const $ZodCheckMaxSize: core.$constructor<$ZodCheckMaxSize> = /*@__PURE__
       return !util.nullish(val) && (val as any).size !== undefined;
     };
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       const curr = (inst._zod.computed.maximum ?? Number.POSITIVE_INFINITY) as number;
       if (def.maximum < curr) inst._zod.computed.maximum = def.maximum;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -490,10 +492,10 @@ export const $ZodCheckMinSize: core.$constructor<$ZodCheckMinSize> = /*@__PURE__
       return !util.nullish(val) && (val as any).size !== undefined;
     };
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       const curr = (inst._zod.computed.minimum ?? Number.NEGATIVE_INFINITY) as number;
       if (def.minimum > curr) inst._zod.computed.minimum = def.minimum;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -539,11 +541,11 @@ export const $ZodCheckSizeEquals: core.$constructor<$ZodCheckSizeEquals> = /*@__
       return !util.nullish(val) && (val as any).size !== undefined;
     };
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.minimum = def.size;
       inst._zod.computed.maximum = def.size;
       inst._zod.computed.size = def.size;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -590,10 +592,10 @@ export const $ZodCheckMaxLength: core.$constructor<$ZodCheckMaxLength> = /*@__PU
       return !util.nullish(val) && (val as any).length !== undefined;
     };
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       const curr = (inst._zod.computed.maximum ?? Number.POSITIVE_INFINITY) as number;
       if (def.maximum < curr) inst._zod.computed.maximum = def.maximum;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -640,10 +642,10 @@ export const $ZodCheckMinLength: core.$constructor<$ZodCheckMinLength> = /*@__PU
       return !util.nullish(val) && (val as any).length !== undefined;
     };
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       const curr = (inst._zod.computed.minimum ?? Number.NEGATIVE_INFINITY) as number;
       if (def.minimum > curr) inst._zod.computed.minimum = def.minimum;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -691,11 +693,11 @@ export const $ZodCheckLengthEquals: core.$constructor<$ZodCheckLengthEquals> = /
       return !util.nullish(val) && (val as any).length !== undefined;
     };
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.minimum = def.length;
       inst._zod.computed.maximum = def.length;
       inst._zod.computed.length = def.length;
-    };
+    });
 
     inst._zod.check = (payload) => {
       const input = payload.value;
@@ -770,10 +772,10 @@ export const $ZodCheckStringFormat: core.$constructor<$ZodCheckStringFormat> = /
   (inst, def) => {
     $ZodCheck.init(inst, def);
 
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.format = def.format;
       if (def.pattern) inst._zod.computed.pattern = def.pattern;
-    };
+    });
 
     inst._zod.check ??= (payload) => {
       if (!def.pattern) throw new Error("Not implemented.");
@@ -934,9 +936,9 @@ export const $ZodCheckIncludes: core.$constructor<$ZodCheckIncludes> = /*@__PURE
 
     const pattern = new RegExp(util.escapeRegex(def.includes));
     def.pattern = pattern;
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.pattern = pattern;
-    };
+    });
 
     inst._zod.check = (payload) => {
       if (payload.value.includes(def.includes, def.position)) return;
@@ -976,9 +978,9 @@ export const $ZodCheckStartsWith: core.$constructor<$ZodCheckStartsWith> = /*@__
 
     const pattern = new RegExp(`^${util.escapeRegex(def.prefix)}.*`);
     def.pattern ??= pattern;
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.pattern = pattern;
-    };
+    });
 
     inst._zod.check = (payload) => {
       if (payload.value.startsWith(def.prefix)) return;
@@ -1018,9 +1020,9 @@ export const $ZodCheckEndsWith: core.$constructor<$ZodCheckEndsWith> = /*@__PURE
 
     const pattern = new RegExp(`.*${util.escapeRegex(def.suffix)}$`);
     def.pattern ??= pattern;
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.pattern = new RegExp(`.*${util.escapeRegex(def.suffix)}$`);
-    };
+    });
 
     inst._zod.check = (payload) => {
       if (payload.value.endsWith(def.suffix)) return;
@@ -1110,9 +1112,9 @@ export const $ZodCheckMimeType: core.$constructor<$ZodCheckMimeType> = /*@__PURE
   (inst, def) => {
     $ZodCheck.init(inst, def);
     const mimeSet = new Set(def.mime);
-    inst._zod.onattach = (inst) => {
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.mime = def.mime;
-    };
+    });
     inst._zod.check = (payload) => {
       if (mimeSet.has(payload.value.type)) return;
       payload.issues.push({
diff --git a/packages/core/src/locales/az.ts b/packages/core/src/locales/az.ts
index 5c25c383..30bce328 100644
--- a/packages/core/src/locales/az.ts
+++ b/packages/core/src/locales/az.ts
@@ -91,7 +91,7 @@ const error: errors.$ZodErrorMap = (issue) => {
     }
     case "invalid_format": {
       const _issue = issue as errors.$ZodStringFormatIssues;
-      if (_issue.format === "starts_with") return `Yanlış mətn: "${issue}" ilə başlamalıdır`;
+      if (_issue.format === "starts_with") return `Yanlış mətn: "${_issue.prefix}" ilə başlamalıdır`;
       if (_issue.format === "ends_with") return `Yanlış mətn: "${_issue.suffix}" ilə bitməlidir`;
       if (_issue.format === "includes") return `Yanlış mətn: "${_issue.includes}" daxil olmalıdır`;
       if (_issue.format === "regex") return `Yanlış mətn: ${_issue.pattern} şablonuna uyğun olmalıdır`;
diff --git a/packages/core/src/locales/es.ts b/packages/core/src/locales/es.ts
index 59a02cf6..ef9af483 100644
--- a/packages/core/src/locales/es.ts
+++ b/packages/core/src/locales/es.ts
@@ -94,7 +94,7 @@ const error: errors.$ZodErrorMap = (issue) => {
     }
     case "invalid_format": {
       const _issue = issue as errors.$ZodStringFormatIssues;
-      if (_issue.format === "starts_with") return `Cadena inválida: debe comenzar con "${issue}"`;
+      if (_issue.format === "starts_with") return `Cadena inválida: debe comenzar con "${_issue.prefix}"`;
       if (_issue.format === "ends_with") return `Cadena inválida: debe terminar en "${_issue.suffix}"`;
       if (_issue.format === "includes") return `Cadena inválida: debe incluir "${_issue.includes}"`;
       if (_issue.format === "regex") return `Cadena inválida: debe coincidir con el patrón ${_issue.pattern}`;
diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts
index f75ade51..176d0959 100644
--- a/packages/core/src/schemas.ts
+++ b/packages/core/src/schemas.ts
@@ -164,7 +164,9 @@ export const $ZodType: core.$constructor<$ZodType> = /*@__PURE__*/ core.$constru
   //
 
   for (const ch of checks) {
-    ch._zod.onattach?.(inst);
+    for (const fn of ch._zod.onattach) {
+      fn(inst);
+    }
   }
 
   if (checks.length === 0) {
@@ -621,11 +623,9 @@ export interface $ZodIPv4 extends $ZodType {
 export const $ZodIPv4: core.$constructor<$ZodIPv4> = /*@__PURE__*/ core.$constructor("$ZodIPv4", (inst, def): void => {
   def.pattern ??= regexes.ipv4;
   $ZodStringFormat.init(inst, def);
-  const superAttach = inst._zod.onattach;
-  inst._zod.onattach = (inst) => {
-    superAttach?.(inst);
+  inst._zod.onattach.push((inst) => {
     inst._zod.computed.format = `ipv4`;
-  };
+  });
 });
 
 //////////////////////////////   ZodIPv6   //////////////////////////////
@@ -645,11 +645,10 @@ export interface $ZodIPv6 extends $ZodType {
 export const $ZodIPv6: core.$constructor<$ZodIPv6> = /*@__PURE__*/ core.$constructor("$ZodIPv6", (inst, def): void => {
   def.pattern ??= regexes.ipv6;
   $ZodStringFormat.init(inst, def);
-  const superAttach = inst._zod.onattach;
-  inst._zod.onattach = (inst) => {
-    superAttach?.(inst);
+
+  inst._zod.onattach.push((inst) => {
     inst._zod.computed.format = `ipv6`;
-  };
+  });
 
   inst._zod.check = (payload) => {
     try {
@@ -685,11 +684,6 @@ export const $ZodCIDRv4: core.$constructor<$ZodCIDRv4> = /*@__PURE__*/ core.$con
   (inst, def): void => {
     def.pattern ??= regexes.cidrv4;
     $ZodStringFormat.init(inst, def);
-    const superAttach = inst._zod.onattach;
-    inst._zod.onattach = (inst) => {
-      superAttach?.(inst);
-      inst._zod.computed.format = `cidrv4`;
-    };
   }
 );
 
@@ -712,11 +706,6 @@ export const $ZodCIDRv6: core.$constructor<$ZodCIDRv6> = /*@__PURE__*/ core.$con
   (inst, def): void => {
     def.pattern ??= regexes.cidrv6; // not used for validation
     $ZodStringFormat.init(inst, def);
-    const superAttach = inst._zod.onattach;
-    inst._zod.onattach = (inst) => {
-      superAttach?.(inst);
-      inst._zod.computed.format = `cidrv6`;
-    };
 
     inst._zod.check = (payload) => {
       const [address, prefix] = payload.value.split("/");
@@ -757,11 +746,7 @@ export const $ZodCIDRv6: core.$constructor<$ZodCIDRv6> = /*@__PURE__*/ core.$con
 //   else if (def.version === "v6") def.pattern ??= regexes.ipv6;
 //   else def.pattern ??= regexes.ip;
 //   $ZodStringFormat.init(inst, def);
-//   const superAttach = inst._zod.onattach;
-//   inst._zod.onattach = (inst) => {
-//     superAttach?.(inst);
-//     inst._zod.computed.format = `ip${def.version ?? ""}`;
-//   };
+
 // });
 
 //////////////////////////////   ZodBase64   //////////////////////////////
@@ -779,11 +764,9 @@ export const $ZodBase64: core.$constructor<$ZodBase64> = /*@__PURE__*/ core.$con
     def.pattern ??= regexes.base64;
     $ZodStringFormat.init(inst, def);
 
-    const superAttach = inst._zod.onattach;
-    inst._zod.onattach = (inst) => {
-      superAttach?.(inst);
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.contentEncoding = "base64";
-    };
+    });
   }
 );
 
@@ -802,11 +785,9 @@ export const $ZodBase64URL: core.$constructor<$ZodBase64URL> = /*@__PURE__*/ cor
     def.pattern ??= regexes.base64url;
     $ZodStringFormat.init(inst, def);
 
-    const superAttach = inst._zod.onattach;
-    inst._zod.onattach = (inst) => {
-      superAttach?.(inst);
+    inst._zod.onattach.push((inst) => {
       inst._zod.computed.contentEncoding = "base64url";
-    };
+    });
   }
 );
 
@@ -1501,7 +1482,9 @@ function handleObjectResult(result: ParsePayload, final: ParsePayload, key: Prop
 
 function handleOptionalObjectResult(result: ParsePayload, final: ParsePayload, key: PropertyKey, input: any) {
   if (result.issues.length) {
+    // validation failed against value schema
     if (input[key] === undefined) {
+      // if input was undefined, ignore the error
       if (key in input) {
         (final.value as any)[key] = undefined;
       }
@@ -1509,8 +1492,10 @@ function handleOptionalObjectResult(result: ParsePayload, final: ParsePayload, k
       final.issues.push(...util.prefixIssues(key, result.issues));
     }
   } else if (result.value === undefined) {
+    // validation returned `undefined`
     if (key in input) (final.value as any)[key] = undefined;
   } else {
+    // non-undefined value
     (final.value as any)[key] = result.value;
   }
 }
@@ -3616,6 +3601,8 @@ export interface $ZodLazyInternals<T extends $ZodType = $ZodType>
   _getter: T;
   pattern: T["_zod"]["pattern"];
   disc: T["_zod"]["disc"];
+  qin: T["_zod"]["qin"];
+  qout: T["_zod"]["qout"];
 }
 
 export interface $ZodLazy<T extends $ZodType = $ZodType> extends $ZodType {
@@ -3631,6 +3618,9 @@ export const $ZodLazy: core.$constructor<$ZodLazy> = /*@__PURE__*/ core.$constru
   inst._zod.parse = (payload, ctx) => {
     return inst._zod._getter._zod.run(payload, ctx);
   };
+  // qin and qout
+  util.defineLazy(inst._zod, "qin", () => inst._zod._getter._zod.qin);
+  util.defineLazy(inst._zod, "qout", () => inst._zod._getter._zod.qout);
 });
 
 ////////////////////////////////////////
diff --git a/packages/core/src/to-json-schema.ts b/packages/core/src/to-json-schema.ts
index 1938b5b1..2aea3a98 100644
--- a/packages/core/src/to-json-schema.ts
+++ b/packages/core/src/to-json-schema.ts
@@ -16,10 +16,10 @@ interface JSONSchemaGeneratorParams {
   unrepresentable?: "throw" | "any";
   /** Arbitrary custom logic that can be used to modify the generated JSON Schema. */
   override?: (ctx: { zodSchema: schemas.$ZodType; jsonSchema: JSONSchema.BaseSchema }) => void;
-  /** Whether to extract the `"input"` or `"output"` type. Relevant to transforms, pipes, defaults, coerced primitives, etc.
+  /** Whether to extract the `"input"` or `"output"` type. Relevant to transforms, Error converting schema to JSONz, defaults, coerced primitives, etc.
    * - `"output" — Default. Convert the output schema.
    * - `"input"` — Convert the input schema. */
-  type?: "input" | "output";
+  io?: "input" | "output";
 }
 
 interface ProcessParams {
@@ -68,12 +68,13 @@ interface Seen {
   defId?: string | undefined;
   // external?: string | undefined;
 }
+
 export class JSONSchemaGenerator {
   metadataRegistry: $ZodRegistry<Record<string, any>>;
   target: "draft-7" | "draft-2020-12";
   unrepresentable: "throw" | "any";
   override: (ctx: { zodSchema: schemas.$ZodType; jsonSchema: JSONSchema.BaseSchema }) => void;
-  type: "input" | "output";
+  io: "input" | "output";
 
   counter = 0;
   seen: Map<schemas.$ZodType, Seen>;
@@ -84,7 +85,7 @@ export class JSONSchemaGenerator {
     this.target = params?.target ?? "draft-2020-12";
     this.unrepresentable = params?.unrepresentable ?? "throw";
     this.override = params?.override ?? (() => {});
-    this.type = params?.type ?? "output";
+    this.io = params?.io ?? "output";
 
     this.seen = new Map();
   }
@@ -297,10 +298,10 @@ export class JSONSchemaGenerator {
       }
       case "union": {
         const json: JSONSchema.BaseSchema = _json as any;
-        json.oneOf = def.options.map((x, i) =>
+        json.anyOf = def.options.map((x, i) =>
           this.process(x, {
             ...params,
-            path: [...params.path, "oneOf", i],
+            path: [...params.path, "anyOf", i],
           })
         );
         break;
@@ -389,24 +390,31 @@ export class JSONSchemaGenerator {
       }
       case "literal": {
         const json: JSONSchema.BaseSchema = _json as any;
+        const vals: (string | number | boolean | null)[] = [];
         for (const val of def.values) {
           if (val === undefined) {
             if (this.unrepresentable === "throw") {
               throw new Error("Literal `undefined` cannot be represented in JSON Schema");
+            } else {
+              // do not add to vals
             }
-          }
-          if (typeof val === "bigint") {
+          } else if (typeof val === "bigint") {
             if (this.unrepresentable === "throw") {
               throw new Error("BigInt literals cannot be represented in JSON Schema");
+            } else {
+              vals.push(Number(val));
             }
+          } else {
+            vals.push(val);
           }
         }
-        if (def.values.length === 1) {
-          json.const = def.values[0] as any;
+        if (vals.length === 0) {
+          // do nothing (an undefined literal was stripped)
+        } else if (vals.length === 1) {
+          const val = vals[0];
+          json.const = val;
         } else {
-          json.enum = def.values.filter(
-            (val) => typeof val === "string" || typeof val === "number" || typeof val === "boolean" || val === null
-          ) as any;
+          json.enum = vals;
         }
         break;
       }
@@ -425,7 +433,7 @@ export class JSONSchemaGenerator {
 
       case "nullable": {
         const inner = this.process(def.innerType, params);
-        _json.oneOf = [inner, { type: "null" }];
+        _json.anyOf = [inner, { type: "null" }];
         break;
       }
       case "nonoptional": {
@@ -464,7 +472,7 @@ export class JSONSchemaGenerator {
         break;
       }
       case "pipe": {
-        const innerType = this.type === "input" ? def.in : def.out;
+        const innerType = this.io === "input" ? def.in : def.out;
         const inner = this.process(innerType, params);
         result.schema = inner;
 
@@ -472,10 +480,8 @@ export class JSONSchemaGenerator {
       }
       case "readonly": {
         const inner = this.process(def.innerType, params);
-        // Object.assign(_json, inner);
-        _json.allOf = [inner];
-        _json.ext;
-        // _json.$ref =
+        Object.assign(_json, inner);
+        // _json.allOf = [inner];
         _json.readOnly = true;
         break;
       }
diff --git a/packages/core/src/versions.ts b/packages/core/src/versions.ts
index bdac7636..3c3d30a8 100644
--- a/packages/core/src/versions.ts
+++ b/packages/core/src/versions.ts
@@ -1,5 +1,5 @@
 export const version = {
   major: 0,
-  minor: 6,
-  patch: 2 as number,
+  minor: 8,
+  patch: 1 as number,
 } as const;
diff --git a/packages/docs/components/ecosystem.tsx b/packages/docs/components/ecosystem.tsx
index d921ac5b..47624d41 100644
--- a/packages/docs/components/ecosystem.tsx
+++ b/packages/docs/components/ecosystem.tsx
@@ -25,7 +25,14 @@ const zodToXConverters: ZodResource[] = [];
 
 const xToZodConverters: ZodResource[] = [];
 
-const mockingLibraries: ZodResource[] = [];
+const mockingLibraries: ZodResource[] = [
+  {
+    name: "zod-schema-faker",
+    url: "https://github.com/soc221b/zod-schema-faker",
+    description: "Generate mock data from zod schemas. Powered by @faker-js/faker and randexp.js.",
+    slug: "soc221b/zod-schema-faker",
+  },
+];
 
 const poweredByZodProjects: ZodResource[] = [];
 
diff --git a/packages/docs/content/api.mdx b/packages/docs/content/api.mdx
index 2cfc775f..9a8ab867 100644
--- a/packages/docs/content/api.mdx
+++ b/packages/docs/content/api.mdx
@@ -823,7 +823,7 @@ const email = z.templateLiteral([
 There are two APIs for defining object schemas: `z.object()` and `z.interface()`. The two APIs are largely identical, with a small (but important!) difference in how *optional properties* are defined.
 {/* 
 <Callout>
-The `z.interface()` API was introduced in Zod 4 as a more flexible and powerful way to represent objects. Optional properties are specified with a `?` prefix in the key. This gives you more control over representing *optionality*:
+The `z.interface()` API was introduced in Zod 4 as a more flexible and powerful way to represent objects. Optional properties are specified with a `?` suffix in the key. This gives you more control over representing *optionality*:
 
 ```ts
 z.interface({
@@ -885,7 +885,7 @@ z.object({ name: z.string().optional() });
 // { name?: string | undefined; }
 ```
 
-By contrast, `z.interface()` can represent both varieties. To make a property "key optional" (e.g. to add the question mark), prefix that key with `?`. 
+By contrast, `z.interface()` can represent both varieties. To make a property "key optional" (e.g. to add the question mark), suffix that key with `?`. 
 
 
 ```ts
@@ -1640,7 +1640,7 @@ function handleResult(result: MyResult){
 }
 ```
 
-You could represent with with a regular `z.union()`. But regular unions are *naive*—they check the input against each option in order and return the first one that passes. This can be slow for large unions.
+You could represent it with a regular `z.union()`. But regular unions are *naive*—they check the input against each option in order and return the first one that passes. This can be slow for large unions.
 
 So Zod provides a `z.discriminatedUnion()` API that uses a *discriminator key* to make parsing more efficient.
 
@@ -1720,6 +1720,7 @@ type EmployedPerson = z.infer<typeof EmployedPerson>;
 
 Record schemas are used to validate types such as `Record<string, number>`.
 
+### `z.record()`
 ```ts
 const IdCache = z.record(z.string(), z.string());
 type IdCache = z.infer<typeof IdCache>; // Record<string, string>
@@ -1730,7 +1731,7 @@ IdCache.parse({
 });
 ```
 
-The key schema can be any Zod schema that as assignable to `string | number | symbol`.
+The key schema can be any Zod schema that is assignable to `string | number | symbol`.
 
 ```ts
 const Keys = z.union([z.string(), z.number(), z.symbol()]);
@@ -1747,7 +1748,7 @@ const Person = z.record(Keys, z.string());
 ```
 
 <Callout>
-  **Partial records** — If you pass a `z.enum` as the first argument to `z.record()`, Zod will exhaustively check that all enum values exist in the input as keys. This behavior agrees with TypeScript:
+  **Zod 4** — In Zod 4, if you pass a `z.enum` as the first argument to `z.record()`, Zod will exhaustively check that all enum values exist in the input as keys. This behavior agrees with TypeScript:
 
   ```ts
   type MyRecord = Record<"a" | "b", string>;
@@ -1755,16 +1756,31 @@ const Person = z.record(Keys, z.string());
   const myRecord: MyRecord = { a: "foo" }; // ❌ missing required key `b`
   ```
 
-  If you want a *partial* record type, you can trick to trick Zod a little bit. This short-circuits the special handling of `z.enum()`.
+  In Zod 3, exhaustiveness was not checked. To replicate the Zod 3 behavior, use `z.partialRecord()`.
+
+</Callout>
+
+### `z.partialRecord()`
+
+If you want a *partial* record type, use `z.partialRecord()`. This skips the special exhaustiveness checks Zod normally runs with `z.enum()` and `z.literal()` key schemas.
+
+```ts
+const Keys = z.enum(["id", "name", "email"]).or(z.never()); 
+const Person = z.partialRecord(Keys, z.string());
+// { id?: string; name?: string; email?: string }
+```
+
+<Accordions>
+<Accordion title="Partial records">
+  If you want a *partial* record type, you'll need to trick Zod a little. This short-circuits the special handling of `z.enum()`.
 
   ```ts
   const Keys = z.enum(["id", "name", "email"]).or(z.never()); 
   const Person = z.record(Keys, z.string());
   // { id?: string; name?: string; email?: string }
   ```
-</Callout>
-
-<Accordions type="single"><Accordion title="A note on numeric keys">
+</Accordion>
+<Accordion title="A note on numeric keys">
 
 Though TypeScript lets you define `Record` types with `number` keys (e.g. `Record<number, unknown>`), numerical keys don't actually exist in JavaScript, which converts all keys to strings.
 
@@ -2274,13 +2290,12 @@ const coercedInt = z.transform((val, ctx) => {
     });
 
     // this is a special constant with type `never`
-    // return it to exit the transform without impacting the inferred return type
+    // returning it lets you exit the transform without impacting the inferred return type
     return z.NEVER; 
   }
 });
 ```
 
-
 Most commonly, transforms are used in conjunction with [Pipes](#pipes). This combination is useful for performing some initial validation, then transforming the parsed data into another form.
 
 
diff --git a/packages/docs/content/basics.mdx b/packages/docs/content/basics.mdx
index f07fa5b5..35f036a1 100644
--- a/packages/docs/content/basics.mdx
+++ b/packages/docs/content/basics.mdx
@@ -14,7 +14,7 @@ Before you can do anything else, you need to define a schema. For the purposes o
 <Tabs groupId="lib" items={["zod", "@zod/mini"]}>
 <Tab value="zod">
 ```ts
-import * as z from "zod";
+import * as z from "zod"; 
 
 const Player = z.interface({ 
   username: z.string(),
diff --git a/packages/docs/content/error-customization.mdx b/packages/docs/content/error-customization.mdx
index 8a0003d2..f13e683e 100644
--- a/packages/docs/content/error-customization.mdx
+++ b/packages/docs/content/error-customization.mdx
@@ -68,45 +68,56 @@ The `zod` library exports a subclass of `$ZodError` called simply `ZodError`. Th
 
 ## The `error` param
 
-Virtually every Zod API accepts an optional parameters object. The `error` param is used to customize the error messages produced by a particular schema.
+Virtually every Zod API accepts an optional error message parameter. 
 
-<Tabs groupId="lib" items={["zod", "@zod/mini"]}>
-<Tab value="zod">
 ```ts
-z.string({ error: "Bad!"}).parse(12);
+z.string("Not a string!");
+```
 
-// throws ZodError {
+This custom error will show up as the `message` property of any validation issues that originate from this schema.
+
+```ts
+z.string("Not a string!").parse(12);
+// ❌ throws ZodError {
 //   issues: [
 //     {
 //       expected: 'string',
 //       code: 'invalid_type',
 //       path: [],
-//       message: 'Bad!'   <-- 👀 custom error message 
+//       message: 'Not a string!'   <-- 👀 custom error message 
 //     }
 //   ]
 // }
 ```
+
+All `z` functions and schema methods accept custom errors.
+
+<Tabs groupId="lib" items={["zod", "@zod/mini"]}>
+<Tab value="zod">
+```ts
+z.string("Bad!");
+z.string().min(5, "Too short!");
+z.uuid("Bad UUID!");
+z.iso.date("Bad date!");
+z.array(z.string(), "Bad array!");
+z.array(z.string()).min(5, "Too few items!");
+z.set(z.string(), "Bad set!");
+```
 </Tab>
 <Tab value="@zod/mini">
 ```ts
-z.string({ error: "Bad string"}).parse(12);
-
-// throws $ZodError {
-//   issues: [
-//     {
-//       expected: 'string',
-//       code: 'invalid_type',
-//       path: [],
-//       message: 'Bad string'
-//     }
-//   ]
-// }
+z.string("Bad!");
+z.string().check(z.minLength(5, "Too short!"));
+z.uuid("Bad UUID!");
+z.iso.date("Bad date!");
+z.array(z.string(), "Bad array!");
+z.array(z.string()).check(z.minLength(5, "Too few items!"));
+z.set(z.string(), "Bad set!");
 ```
-
 </Tab>
 </Tabs>
 
-Nearly all functions and methods support `error`.
+If you prefer, you can pass a params object with an `error` parameter instead.
 
 <Tabs groupId="lib" items={["zod", "@zod/mini"]}>
 <Tab value="zod">
@@ -118,9 +129,6 @@ z.iso.date({ error: "Bad date!" });
 z.array(z.string(), { error: "Bad array!" });
 z.array(z.string()).min(5, { error: "Too few items!" });
 z.set(z.string(), { error: "Bad set!" });
-z.array(z.string(), { error: "Bad array!" });
-z.set(z.string(), { error: "Bad set!" });
-z.array(z.string(), { error: "Bad array!" });
 ```
 </Tab>
 <Tab value="@zod/mini">
@@ -132,15 +140,12 @@ z.iso.date({ error: "Bad date!" });
 z.array(z.string(), { error: "Bad array!" });
 z.array(z.string()).check(z.minLength(5, { error: "Too few items!" }));
 z.set(z.string(), { error: "Bad set!" });
-z.array(z.string(), { error: "Bad array!" });
-z.set(z.string(), { error: "Bad set!" });
-z.array(z.string(), { error: "Bad array!" });
 ```
 
 </Tab>
 </Tabs>
 
-The `error` param optionally accepts a function, also known as an **error map**. This function will be called at parse time.
+The `error` param optionally accepts a function. This function will be called at parse time if a valiation error occurs.
 
 ```ts
 z.string({ error: ()=>`[${Date.now()}]: Validation failure.` });
@@ -256,7 +261,6 @@ const result = schema.safeParse(12, {
 })
 ```
 
-
 ## Global error customization
 
 To specify a global error map, use `z.config()` to set Zod's `customError` configuration setting:
@@ -314,7 +318,20 @@ z.config(en());
 </Tab>
 </Tabs>
 
-For convenience, you can also references these from the `z.core` namespace exports from `zod`/`@zod/mini`.
+To lazily load a locale, consider dynamic imports:
+
+```ts
+import * as z from "zod";
+
+async function loadLocale (locale: string) {
+  const { default: locale } = await import(`@zod/core/locales/${locale}`);
+  z.config(locale());
+};
+
+await loadLocale("fr");
+```
+
+For convenience, all locales are exported as `z.locales` feom `zod`/`@zod/mini`.
 
 <Tabs groupId="lib" items={["zod", "@zod/mini"]}>
 <Tab value="zod">
@@ -340,3 +357,35 @@ The following locales are available:
 
 - `az` — Azerbaijani
 - `en` — English
+
+## Error precedence
+
+Below is a quick reference for determining error precedence: if multiple error customizations have been defined, which one takes priority? From *highest to lowest* priority:
+
+1. **Schema-level error** — Any error message "hard coded" into a schema definition.
+
+  ```ts
+  z.string("Not a string!");
+  ```
+
+2. **Per-parse error** — A custom error map passed into the `.parse()` method.
+
+  ```ts
+  z.string().parse(12, {
+    error: (iss) => "My custom error"
+  });
+  ```
+
+3. **Global error map** — A custom error map passed into `z.config()`.
+
+  ```ts
+  z.config({
+    customError: (iss) => "My custom error"
+  });
+
+4. **Locale error map** — A custom error map passed into `z.config()`.
+
+  ```ts
+  z.config(z.locales.en());
+  ```
+
diff --git a/packages/docs/content/json-schema.mdx b/packages/docs/content/json-schema.mdx
index b11a64e6..41bc3297 100644
--- a/packages/docs/content/json-schema.mdx
+++ b/packages/docs/content/json-schema.mdx
@@ -414,9 +414,9 @@ z.toJSONSchema(mySchema, {
 ```
 
 
-### `pipes`
+### `io`
 
-Pipes contain an input and and output schema. By default, the result of `z.toJSONSchema` represents the *output schema*; use `"pipe": "input"` to extract the input type instead.
+Some schema types have different input and output types, e.g. `ZodPipe`, `ZodDefault`, and coerced primitives. By default, the result of `z.toJSONSchema` represents the *output type*; use `"io": "input"` to extract the input type instead.
 
 ```ts
 const mySchema = z.string().transform(val => val.length).pipe(z.number());
@@ -425,7 +425,7 @@ const mySchema = z.string().transform(val => val.length).pipe(z.number());
 const jsonSchema = z.toJSONSchema(mySchema); 
 // => { type: "number" }
 
-const jsonSchema = z.toJSONSchema(mySchema, { pipe: "input" }); 
+const jsonSchema = z.toJSONSchema(mySchema, { io: "input" }); 
 // => { type: "string" }
 ```
 
diff --git a/packages/docs/content/packages/core.mdx b/packages/docs/content/packages/core.mdx
index 4fa7dd05..8ac5e05e 100644
--- a/packages/docs/content/packages/core.mdx
+++ b/packages/docs/content/packages/core.mdx
@@ -416,12 +416,12 @@ When your user installs `zod` or `@zod/mini`, their package manager will automat
 <Callout>
 ### Versioning
 
-While Zod 4 is in beta, the [`@zod/core`](https://www.npmjs.com/package/@zod/core) package is published as `latest` in the `v0.x` range ("initial development" according to semver). Once the first stable release release of Zod 4, at which point `v1.0.0` will be released.
+While Zod 4 is in beta, the [`@zod/core`](https://www.npmjs.com/package/@zod/core) package is published as `latest` in the `v0.x` range ("initial development" according to semver). The `v1.0.0` release will accompany the first stable release of `zod@4.0`.
 
 | Version | Semver Range | Description                                                                 |
 |--------------|--------------|-----------------------------------------------------------------------------|
 | Beta         | `@zod/core@^0.0.1`     | Versions will say in `v0.x` range during the beta |
-| Stable       | `@zod/core@^1.0.0`     | Once the stable version is released, you'll need to bump the version to `^1.0.0`.  |
+| Stable       | `@zod/core@^1.0.0`     | It will go stable alongside `zod@4`. At this point, you'll need to bump the version to `^1.0.0`.  |
 </Callout>
 
 If Zod isn't *always* needed for your library (say, if you are building a framework that supports a number of different schema libraries), use `"peerDependenciesMeta"` to mark `@zod/core` as optional.
diff --git a/packages/mini/src/schemas.ts b/packages/mini/src/schemas.ts
index 82fb9503..4f5cfe63 100644
--- a/packages/mini/src/schemas.ts
+++ b/packages/mini/src/schemas.ts
@@ -49,7 +49,9 @@ export const ZodMiniType: core.$constructor<ZodMiniType> = /*@__PURE__*/ core.$c
         ...def,
         checks: [
           ...(def.checks ?? []),
-          ...checks.map((ch) => (typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" } } } : ch)),
+          ...checks.map((ch) =>
+            typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" }, onattach: [] } } : ch
+          ),
         ],
       });
     };
@@ -669,7 +671,7 @@ export const ZodMiniArray: core.$constructor<ZodMiniArray> = /*@__PURE__*/ core.
   }
 );
 
-export function array<T extends SomeType>(element: T, params?: core.$ZodArrayParams): ZodMiniArray<T>;
+export function array<T extends SomeType>(element: T, params?: string | core.$ZodArrayParams): ZodMiniArray<T>;
 export function array<T extends SomeType>(element: SomeType, params?: any): ZodMiniArray<T> {
   return new ZodMiniArray({
     type: "array",
@@ -726,7 +728,7 @@ export const ZodMiniInterface: core.$constructor<ZodMiniInterface> = /*@__PURE__
 
 function _interface<T extends core.$ZodLooseShape>(
   shape: T,
-  params?: core.$ZodInterfaceParams,
+  params?: string | core.$ZodInterfaceParams,
   Class: util.Constructor<ZodMiniInterface> = ZodMiniInterface
 ): ZodMiniInterface<util.CleanInterfaceShape<T>, util.InitInterfaceParams<T, {}>> {
   const cleaned = util.cached(() => util.cleanInterfaceShape(shape));
@@ -751,7 +753,7 @@ export { _interface as interface };
 
 export function strictInterface<T extends core.$ZodLooseShape>(
   shape: T,
-  params?: core.$ZodInterfaceParams
+  params?: string | core.$ZodInterfaceParams
 ): ZodMiniInterface<util.CleanInterfaceShape<T>, util.InitInterfaceParams<T, {}>> {
   const cleaned = util.cached(() => util.cleanInterfaceShape(shape));
   const def: core.$ZodInterfaceDef = {
@@ -775,7 +777,7 @@ export function strictInterface<T extends core.$ZodLooseShape>(
 
 export function looseInterface<T extends core.$ZodLooseShape>(
   shape: T,
-  params?: core.$ZodInterfaceParams
+  params?: string | core.$ZodInterfaceParams
 ): ZodMiniInterface<util.CleanInterfaceShape<T>, util.InitInterfaceParams<T, Record<string, unknown>>> {
   const cleaned = util.cached(() => util.cleanInterfaceShape(shape));
   const def: core.$ZodInterfaceDef = {
@@ -811,7 +813,7 @@ export const ZodMiniObject: core.$constructor<ZodMiniObject> = /*@__PURE__*/ cor
 );
 export function object<T extends core.$ZodShape = Record<never, SomeType>>(
   shape?: T,
-  params?: core.$ZodObjectLikeParams
+  params?: string | core.$ZodObjectLikeParams
 ): ZodMiniObject<T, {}> {
   const def: core.$ZodObjectDef = {
     type: "object",
@@ -826,7 +828,10 @@ export function object<T extends core.$ZodShape = Record<never, SomeType>>(
 
 // strictObject
 
-export function strictObject<T extends core.$ZodShape>(shape: T, params?: core.$ZodObjectParams): ZodMiniObject<T, {}> {
+export function strictObject<T extends core.$ZodShape>(
+  shape: T,
+  params?: string | core.$ZodObjectParams
+): ZodMiniObject<T, {}> {
   return new ZodMiniObject({
     type: "object",
     shape: shape as core.$ZodShape,
@@ -842,7 +847,7 @@ export function strictObject<T extends core.$ZodShape>(shape: T, params?: core.$
 
 export function looseObject<T extends core.$ZodShape>(
   shape: T,
-  params?: core.$ZodObjectParams
+  params?: string | core.$ZodObjectParams
 ): ZodMiniObject<T, { [k: string]: unknown }> {
   return new ZodMiniObject({
     type: "object",
@@ -1069,7 +1074,10 @@ export const ZodMiniUnion: core.$constructor<ZodMiniUnion> = /*@__PURE__*/ core.
   }
 );
 
-export function union<const T extends readonly SomeType[]>(options: T, params?: core.$ZodUnionParams): ZodMiniUnion<T> {
+export function union<const T extends readonly SomeType[]>(
+  options: T,
+  params?: string | core.$ZodUnionParams
+): ZodMiniUnion<T> {
   return new ZodMiniUnion({
     type: "union",
     options,
@@ -1100,7 +1108,7 @@ export interface $ZodTypeDiscriminable extends ZodMiniType {
 
 export function discriminatedUnion<Types extends [$ZodTypeDiscriminable, ...$ZodTypeDiscriminable[]]>(
   options: Types,
-  params?: core.$ZodDiscriminatedUnionParams
+  params?: string | core.$ZodDiscriminatedUnionParams
 ): ZodMiniDiscriminatedUnion<Types> {
   return new ZodMiniDiscriminatedUnion({
     type: "union",
@@ -1124,7 +1132,7 @@ export const ZodMiniIntersection: core.$constructor<ZodMiniIntersection> = /*@__
 export function intersection<T extends SomeType, U extends SomeType>(
   left: T,
   right: U,
-  params?: core.$ZodIntersectionParams
+  params?: string | core.$ZodIntersectionParams
 ): ZodMiniIntersection<T, U> {
   return new ZodMiniIntersection({
     type: "intersection",
@@ -1151,18 +1159,18 @@ export const ZodMiniTuple: core.$constructor<ZodMiniTuple> = /*@__PURE__*/ core.
 
 export function tuple<T extends readonly [SomeType, ...SomeType[]]>(
   items: T,
-  params?: core.$ZodTupleParams
+  params?: string | core.$ZodTupleParams
 ): ZodMiniTuple<T, null>;
 export function tuple<T extends readonly [SomeType, ...SomeType[]], Rest extends SomeType>(
   items: T,
   rest: Rest,
-  params?: core.$ZodTupleParams
+  params?: string | core.$ZodTupleParams
 ): ZodMiniTuple<T, Rest>;
-export function tuple(items: [], params?: core.$ZodTupleParams): ZodMiniTuple<[], null>;
+export function tuple(items: [], params?: string | core.$ZodTupleParams): ZodMiniTuple<[], null>;
 export function tuple(
   items: SomeType[],
-  _paramsOrRest?: core.$ZodTupleParams | SomeType,
-  _params?: core.$ZodTupleParams
+  _paramsOrRest?: string | core.$ZodTupleParams | SomeType,
+  _params?: string | core.$ZodTupleParams
 ) {
   const hasRest = _paramsOrRest instanceof core.$ZodType;
   const params = hasRest ? _params : _paramsOrRest;
@@ -1191,7 +1199,7 @@ export const ZodMiniRecord: core.$constructor<ZodMiniRecord> = /*@__PURE__*/ cor
 export function record<Key extends core.$ZodRecordKey, Value extends SomeType>(
   keyType: Key,
   valueType: Value,
-  params?: core.$ZodRecordParams
+  params?: string | core.$ZodRecordParams
 ): ZodMiniRecord<Key, Value> {
   return new ZodMiniRecord({
     type: "record",
@@ -1200,6 +1208,18 @@ export function record<Key extends core.$ZodRecordKey, Value extends SomeType>(
     ...util.normalizeParams(params),
   }) as ZodMiniRecord<Key, Value>;
 }
+export function partialRecord<Key extends core.$ZodRecordKey, Value extends SomeType>(
+  keyType: Key,
+  valueType: Value,
+  params?: string | core.$ZodRecordParams
+): ZodMiniRecord<ZodMiniUnion<[Key, ZodMiniNever]>, Value> {
+  return new ZodMiniRecord({
+    type: "record",
+    keyType: union([keyType, never()]),
+    valueType,
+    ...util.normalizeParams(params),
+  }) as ZodMiniRecord<Key, Value>;
+}
 
 // ZodMiniMap
 export interface ZodMiniMap<Key extends SomeType = SomeType, Value extends SomeType = SomeType> extends ZodMiniType {
@@ -1213,7 +1233,7 @@ export const ZodMiniMap: core.$constructor<ZodMiniMap> = /*@__PURE__*/ core.$con
 export function map<Key extends SomeType, Value extends SomeType>(
   keyType: Key,
   valueType: Value,
-  params?: core.$ZodMapParams
+  params?: string | core.$ZodMapParams
 ): ZodMiniMap<Key, Value> {
   return new ZodMiniMap({
     type: "map",
@@ -1232,7 +1252,7 @@ export const ZodMiniSet: core.$constructor<ZodMiniSet> = /*@__PURE__*/ core.$con
   ZodMiniType.init(inst, def);
 });
 
-export function set<Value extends SomeType>(valueType: Value, params?: core.$ZodSetParams): ZodMiniSet<Value> {
+export function set<Value extends SomeType>(valueType: Value, params?: string | core.$ZodSetParams): ZodMiniSet<Value> {
   return new ZodMiniSet({
     type: "set",
     valueType,
@@ -1252,9 +1272,12 @@ export const ZodMiniEnum: core.$constructor<ZodMiniEnum> = /*@__PURE__*/ core.$c
   }
 );
 
-function _enum<const T extends string[]>(values: T, params?: core.$ZodEnumParams): ZodMiniEnum<util.ToEnum<T[number]>>;
-function _enum<T extends util.EnumLike>(entries: T, params?: core.$ZodEnumParams): ZodMiniEnum<T>;
-function _enum(values: any, params?: core.$ZodEnumParams) {
+function _enum<const T extends string[]>(
+  values: T,
+  params?: string | core.$ZodEnumParams
+): ZodMiniEnum<util.ToEnum<T[number]>>;
+function _enum<T extends util.EnumLike>(entries: T, params?: string | core.$ZodEnumParams): ZodMiniEnum<T>;
+function _enum(values: any, params?: string | core.$ZodEnumParams) {
   const entries: any = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
 
   return new ZodMiniEnum({
@@ -1272,7 +1295,7 @@ export { _enum as enum };
  * z.enum(Colors);
  * ```
  */
-export function nativeEnum<T extends util.EnumLike>(entries: T, params?: core.$ZodEnumParams): ZodMiniEnum<T> {
+export function nativeEnum<T extends util.EnumLike>(entries: T, params?: string | core.$ZodEnumParams): ZodMiniEnum<T> {
   return new ZodMiniEnum({
     type: "enum",
     entries,
@@ -1294,9 +1317,12 @@ export const ZodMiniLiteral: core.$constructor<ZodMiniLiteral> = /*@__PURE__*/ c
 
 export function literal<const T extends Array<util.Literal>>(
   value: T,
-  params?: core.$ZodLiteralParams
+  params?: string | core.$ZodLiteralParams
 ): ZodMiniLiteral<T[number]>;
-export function literal<const T extends util.Literal>(value: T, params?: core.$ZodLiteralParams): ZodMiniLiteral<T>;
+export function literal<const T extends util.Literal>(
+  value: T,
+  params?: string | core.$ZodLiteralParams
+): ZodMiniLiteral<T>;
 export function literal(value: any, params: any) {
   return new ZodMiniLiteral({
     type: "literal",
@@ -1335,7 +1361,7 @@ export const ZodMiniTransform: core.$constructor<ZodMiniTransform> = /*@__PURE__
 
 export function transform<I = unknown, O = I>(
   fn: (input: I, ctx: core.ParsePayload) => O,
-  params?: core.$ZodTransformParams
+  params?: string | core.$ZodTransformParams
 ): ZodMiniTransform<Awaited<O>, I> {
   return new ZodMiniTransform({
     type: "transform",
@@ -1356,7 +1382,10 @@ export const ZodMiniOptional: core.$constructor<ZodMiniOptional> = /*@__PURE__*/
   }
 );
 
-export function optional<T extends SomeType>(innerType: T, params?: core.$ZodOptionalParams): ZodMiniOptional<T> {
+export function optional<T extends SomeType>(
+  innerType: T,
+  params?: string | core.$ZodOptionalParams
+): ZodMiniOptional<T> {
   return new ZodMiniOptional({
     type: "optional",
     innerType,
@@ -1376,7 +1405,10 @@ export const ZodMiniNullable: core.$constructor<ZodMiniNullable> = /*@__PURE__*/
   }
 );
 
-export function nullable<T extends SomeType>(innerType: T, params?: core.$ZodNullableParams): ZodMiniNullable<T> {
+export function nullable<T extends SomeType>(
+  innerType: T,
+  params?: string | core.$ZodNullableParams
+): ZodMiniNullable<T> {
   return new ZodMiniNullable({
     type: "nullable",
     innerType,
@@ -1404,7 +1436,7 @@ export const ZodMiniDefault: core.$constructor<ZodMiniDefault> = /*@__PURE__*/ c
 export function _default<T extends SomeType>(
   innerType: T,
   defaultValue: util.NoUndefined<core.output<T>> | (() => util.NoUndefined<core.output<T>>),
-  params?: core.$ZodDefaultParams
+  params?: string | core.$ZodDefaultParams
 ): ZodMiniDefault<T> {
   return new ZodMiniDefault({
     type: "default",
@@ -1428,7 +1460,7 @@ export const ZodMiniNonOptional: core.$constructor<ZodMiniNonOptional> = /*@__PU
 
 export function nonoptional<T extends SomeType>(
   innerType: T,
-  params?: core.$ZodNonOptionalParams
+  params?: string | core.$ZodNonOptionalParams
 ): ZodMiniNonOptional<T> {
   return new ZodMiniNonOptional({
     type: "nonoptional",
@@ -1449,7 +1481,7 @@ export const ZodMiniSuccess: core.$constructor<ZodMiniSuccess> = /*@__PURE__*/ c
   }
 );
 
-export function success<T extends SomeType>(innerType: T, params?: core.$ZodSuccessParams): ZodMiniSuccess<T> {
+export function success<T extends SomeType>(innerType: T, params?: string | core.$ZodSuccessParams): ZodMiniSuccess<T> {
   return new ZodMiniSuccess({
     type: "success",
     innerType,
@@ -1472,7 +1504,7 @@ export const ZodMiniCatch: core.$constructor<ZodMiniCatch> = /*@__PURE__*/ core.
 function _catch<T extends SomeType>(
   innerType: T,
   catchValue: core.output<T> | ((ctx: core.$ZodCatchCtx) => core.output<T>),
-  params?: core.$ZodCatchParams
+  params?: string | core.$ZodCatchParams
 ): ZodMiniCatch<T> {
   return new ZodMiniCatch({
     type: "catch",
@@ -1513,8 +1545,8 @@ export const ZodMiniPipe: core.$constructor<ZodMiniPipe> = /*@__PURE__*/ core.$c
 export function pipe<
   const A extends core.$ZodType,
   B extends core.$ZodType<unknown, core.output<A>> = core.$ZodType<unknown, core.output<A>>,
->(in_: A, out: B | core.$ZodType<unknown, core.output<A>>, params?: core.$ZodPipeParams): ZodMiniPipe<A, B>;
-export function pipe(in_: core.$ZodType, out: core.$ZodType, params?: core.$ZodPipeParams) {
+>(in_: A, out: B | core.$ZodType<unknown, core.output<A>>, params?: string | core.$ZodPipeParams): ZodMiniPipe<A, B>;
+export function pipe(in_: core.$ZodType, out: core.$ZodType, params?: string | core.$ZodPipeParams) {
   return new ZodMiniPipe({
     type: "pipe",
     in: in_,
@@ -1544,7 +1576,10 @@ export const ZodMiniReadonly: core.$constructor<ZodMiniReadonly> = /*@__PURE__*/
   }
 );
 
-export function readonly<T extends SomeType>(innerType: T, params?: core.$ZodReadonlyParams): ZodMiniReadonly<T> {
+export function readonly<T extends SomeType>(
+  innerType: T,
+  params?: string | core.$ZodReadonlyParams
+): ZodMiniReadonly<T> {
   return new ZodMiniReadonly({
     type: "readonly",
     innerType,
@@ -1566,7 +1601,7 @@ export const ZodMiniTemplateLiteral: core.$constructor<ZodMiniTemplateLiteral> =
 
 export function templateLiteral<const Parts extends core.$TemplateLiteralPart[]>(
   parts: Parts,
-  params?: core.$ZodTemplateLiteralParams
+  params?: string | core.$ZodTemplateLiteralParams
 ): ZodMiniTemplateLiteral<core.$PartsToTemplateLiteral<Parts>> {
   return new ZodMiniTemplateLiteral({
     type: "template_literal",
@@ -1610,7 +1645,7 @@ export const ZodMiniPromise: core.$constructor<ZodMiniPromise> = /*@__PURE__*/ c
   }
 );
 
-export function promise<T extends SomeType>(innerType: T, params?: core.$ZodPromiseParams): ZodMiniPromise<T> {
+export function promise<T extends SomeType>(innerType: T, params?: string | core.$ZodPromiseParams): ZodMiniPromise<T> {
   return new ZodMiniPromise({
     type: "promise",
     innerType,
@@ -1631,7 +1666,7 @@ export const ZodMiniCustom: core.$constructor<ZodMiniCustom> = /*@__PURE__*/ cor
 );
 
 // custom checks
-export function check<O = unknown>(fn: core.CheckFn<O>, params?: core.$ZodCustomParams): core.$ZodCheck<O> {
+export function check<O = unknown>(fn: core.CheckFn<O>, params?: string | core.$ZodCustomParams): core.$ZodCheck<O> {
   const ch = new core.$ZodCheck({
     check: "custom",
     ...util.normalizeParams(params),
@@ -1689,12 +1724,13 @@ function _instanceof<T extends typeof Class>(
 export { _instanceof as instanceof };
 
 // stringbool
-export const stringbool: (_params?: core.$ZodStringBoolParams) => ZodMiniPipe<ZodMiniUnknown, ZodMiniBoolean<boolean>> =
-  /* @__PURE__ */ core._stringbool.bind(null, {
-    Pipe: ZodMiniPipe,
-    Boolean: ZodMiniBoolean,
-    Unknown: ZodMiniUnknown,
-  }) as any;
+export const stringbool: (
+  _params?: string | core.$ZodStringBoolParams
+) => ZodMiniPipe<ZodMiniUnknown, ZodMiniBoolean<boolean>> = /* @__PURE__ */ core._stringbool.bind(null, {
+  Pipe: ZodMiniPipe,
+  Boolean: ZodMiniBoolean,
+  Unknown: ZodMiniUnknown,
+}) as any;
 
 // json
 export type ZodMiniJSONSchema = ZodMiniLazy<
diff --git a/packages/zod/src/schemas.ts b/packages/zod/src/schemas.ts
index 77f3f1d3..9e2854ff 100644
--- a/packages/zod/src/schemas.ts
+++ b/packages/zod/src/schemas.ts
@@ -64,12 +64,12 @@ export interface ZodType<out Output = unknown, out Input = unknown> extends core
   overwrite(fn: (x: core.output<this>) => core.output<this>): this;
 
   // wrappers
-  optional(params?: core.$ZodOptionalParams): ZodOptional<this>;
-  nonoptional(params?: core.$ZodNonOptionalParams): ZodNonOptional<this>;
-  nullable(params?: core.$ZodNullableParams): ZodNullable<this>;
+  optional(params?: string | core.$ZodOptionalParams): ZodOptional<this>;
+  nonoptional(params?: string | core.$ZodNonOptionalParams): ZodNonOptional<this>;
+  nullable(params?: string | core.$ZodNullableParams): ZodNullable<this>;
   nullish(): ZodOptional<ZodNullable<this>>;
-  default(def: util.NoUndefined<core.output<this>>, params?: core.$ZodDefaultParams): ZodDefault<this>;
-  default(def: () => util.NoUndefined<core.output<this>>, params?: core.$ZodDefaultParams): ZodDefault<this>;
+  default(def: util.NoUndefined<core.output<this>>, params?: string | core.$ZodDefaultParams): ZodDefault<this>;
+  default(def: () => util.NoUndefined<core.output<this>>, params?: string | core.$ZodDefaultParams): ZodDefault<this>;
   array(): ZodArray<this>;
   or<T extends core.$ZodType>(option: T): ZodUnion<[this, T]>;
   and<T extends core.$ZodType>(incoming: T): ZodIntersection<this, T>;
@@ -107,7 +107,9 @@ export const ZodType: core.$constructor<ZodType> = /*@__PURE__*/ core.$construct
       ...def,
       checks: [
         ...(def.checks ?? []),
-        ...checks.map((ch) => (typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" } } } : ch)),
+        ...checks.map((ch) =>
+          typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" }, onattach: [] } } : ch
+        ),
       ],
     });
   };
@@ -257,7 +259,7 @@ export interface _ZodString<Input = unknown> extends ZodType {
   // json(params?: string | core.$ZodCheckJSONStringParams): this;
 
   // miscellaneous checks
-  regex(regex: RegExp, params?: core.$ZodCheckRegexParams): this;
+  regex(regex: RegExp, params?: string | core.$ZodCheckRegexParams): this;
   includes(value: string, params?: { message?: string; position?: number }): this;
   startsWith(value: string, params?: string | core.$ZodCheckStartsWithParams): this;
   endsWith(value: string, params?: string | core.$ZodCheckEndsWithParams): this;
@@ -364,7 +366,7 @@ export interface ZodEmail extends ZodStringFormat<"email"> {
 export const ZodEmail: core.$constructor<ZodEmail> = /*@__PURE__*/ core.$constructor("ZodEmail", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodEmail.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function email(params?: string | core.$ZodEmailParams): ZodEmail {
@@ -378,7 +380,7 @@ export interface ZodGUID extends ZodStringFormat<"guid"> {
 export const ZodGUID: core.$constructor<ZodGUID> = /*@__PURE__*/ core.$constructor("ZodGUID", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodGUID.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function guid(params?: string | core.$ZodGUIDParams): ZodGUID {
@@ -392,7 +394,7 @@ export interface ZodUUID extends ZodStringFormat<"uuid"> {
 export const ZodUUID: core.$constructor<ZodUUID> = /*@__PURE__*/ core.$constructor("ZodUUID", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodUUID.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function uuid(params?: string | core.$ZodUUIDParams): ZodUUID {
@@ -422,7 +424,7 @@ export interface ZodURL extends ZodStringFormat<"url"> {
 export const ZodURL: core.$constructor<ZodURL> = /*@__PURE__*/ core.$constructor("ZodURL", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodURL.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function url(params?: string | core.$ZodURLParams): ZodURL {
@@ -436,7 +438,7 @@ export interface ZodEmoji extends ZodStringFormat<"emoji"> {
 export const ZodEmoji: core.$constructor<ZodEmoji> = /*@__PURE__*/ core.$constructor("ZodEmoji", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodEmoji.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function emoji(params?: string | core.$ZodEmojiParams): ZodEmoji {
@@ -450,7 +452,7 @@ export interface ZodNanoID extends ZodStringFormat<"nanoid"> {
 export const ZodNanoID: core.$constructor<ZodNanoID> = /*@__PURE__*/ core.$constructor("ZodNanoID", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodNanoID.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function nanoid(params?: string | core.$ZodNanoIDParams): ZodNanoID {
@@ -464,7 +466,7 @@ export interface ZodCUID extends ZodStringFormat<"cuid"> {
 export const ZodCUID: core.$constructor<ZodCUID> = /*@__PURE__*/ core.$constructor("ZodCUID", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodCUID.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function cuid(params?: string | core.$ZodCUIDParams): ZodCUID {
@@ -478,7 +480,7 @@ export interface ZodCUID2 extends ZodStringFormat<"cuid2"> {
 export const ZodCUID2: core.$constructor<ZodCUID2> = /*@__PURE__*/ core.$constructor("ZodCUID2", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodCUID2.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function cuid2(params?: string | core.$ZodCUID2Params): ZodCUID2 {
@@ -492,7 +494,7 @@ export interface ZodULID extends ZodStringFormat<"ulid"> {
 export const ZodULID: core.$constructor<ZodULID> = /*@__PURE__*/ core.$constructor("ZodULID", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodULID.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function ulid(params?: string | core.$ZodULIDParams): ZodULID {
@@ -506,7 +508,7 @@ export interface ZodXID extends ZodStringFormat<"xid"> {
 export const ZodXID: core.$constructor<ZodXID> = /*@__PURE__*/ core.$constructor("ZodXID", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodXID.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function xid(params?: string | core.$ZodXIDParams): ZodXID {
@@ -520,7 +522,7 @@ export interface ZodKSUID extends ZodStringFormat<"ksuid"> {
 export const ZodKSUID: core.$constructor<ZodKSUID> = /*@__PURE__*/ core.$constructor("ZodKSUID", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodKSUID.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function ksuid(params?: string | core.$ZodKSUIDParams): ZodKSUID {
@@ -534,7 +536,7 @@ export function ksuid(params?: string | core.$ZodKSUIDParams): ZodKSUID {
 // export const ZodIP: core.$constructor<ZodIP> = /*@__PURE__*/ core.$constructor("ZodIP", (inst, def) => {
 //   // ZodStringFormat.init(inst, def);
 //   core.$ZodIP.init(inst, def);
-//   ZodType.init(inst, def);
+//   ZodStringFormat.init(inst, def);
 // });
 
 // export function ip(params?: string | core.$ZodIPParams): ZodIP {
@@ -548,7 +550,7 @@ export interface ZodIPv4 extends ZodStringFormat<"ipv4"> {
 export const ZodIPv4: core.$constructor<ZodIPv4> = /*@__PURE__*/ core.$constructor("ZodIPv4", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodIPv4.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function ipv4(params?: string | core.$ZodIPv4Params): ZodIPv4 {
@@ -562,7 +564,7 @@ export interface ZodIPv6 extends ZodStringFormat<"ipv6"> {
 export const ZodIPv6: core.$constructor<ZodIPv6> = /*@__PURE__*/ core.$constructor("ZodIPv6", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodIPv6.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 export function ipv6(params?: string | core.$ZodIPv6Params): ZodIPv6 {
   return core._ipv6(ZodIPv6, params);
@@ -574,7 +576,7 @@ export interface ZodCIDRv4 extends ZodStringFormat<"cidrv4"> {
 }
 export const ZodCIDRv4: core.$constructor<ZodCIDRv4> = /*@__PURE__*/ core.$constructor("ZodCIDRv4", (inst, def) => {
   core.$ZodCIDRv4.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function cidrv4(params?: string | core.$ZodCIDRv4Params): ZodCIDRv4 {
@@ -587,7 +589,7 @@ export interface ZodCIDRv6 extends ZodStringFormat<"cidrv6"> {
 }
 export const ZodCIDRv6: core.$constructor<ZodCIDRv6> = /*@__PURE__*/ core.$constructor("ZodCIDRv6", (inst, def) => {
   core.$ZodCIDRv6.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function cidrv6(params?: string | core.$ZodCIDRv6Params): ZodCIDRv6 {
@@ -601,7 +603,7 @@ export interface ZodBase64 extends ZodStringFormat<"base64"> {
 export const ZodBase64: core.$constructor<ZodBase64> = /*@__PURE__*/ core.$constructor("ZodBase64", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodBase64.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 export function base64(params?: string | core.$ZodBase64Params): ZodBase64 {
   return core._base64(ZodBase64, params);
@@ -616,7 +618,7 @@ export const ZodBase64URL: core.$constructor<ZodBase64URL> = /*@__PURE__*/ core.
   (inst, def) => {
     // ZodStringFormat.init(inst, def);
     core.$ZodBase64URL.init(inst, def);
-    ZodType.init(inst, def);
+    ZodStringFormat.init(inst, def);
   }
 );
 export function base64url(params?: string | core.$ZodBase64URLParams): ZodBase64URL {
@@ -630,7 +632,7 @@ export interface ZodE164 extends ZodStringFormat<"e164"> {
 export const ZodE164: core.$constructor<ZodE164> = /*@__PURE__*/ core.$constructor("ZodE164", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodE164.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function e164(params?: string | core.$ZodE164Params): ZodE164 {
@@ -644,7 +646,7 @@ export interface ZodJWT extends ZodStringFormat<"jwt"> {
 export const ZodJWT: core.$constructor<ZodJWT> = /*@__PURE__*/ core.$constructor("ZodJWT", (inst, def) => {
   // ZodStringFormat.init(inst, def);
   core.$ZodJWT.init(inst, def);
-  ZodType.init(inst, def);
+  ZodStringFormat.init(inst, def);
 });
 
 export function jwt(params?: string | core.$ZodJWTParams): ZodJWT {
@@ -1001,7 +1003,7 @@ export const ZodArray: core.$constructor<ZodArray> = /*@__PURE__*/ core.$constru
   inst.length = (len, params) => inst.check(checks.length(len, params));
 });
 
-export function array<T extends core.$ZodType>(element: T, params?: core.$ZodArrayParams): ZodArray<T>;
+export function array<T extends core.$ZodType>(element: T, params?: string | core.$ZodArrayParams): ZodArray<T>;
 export function array<T extends core.$ZodType>(element: core.$ZodType, params?: any): ZodArray<T> {
   return core._array(ZodArray, element, params) as any;
 }
@@ -1176,7 +1178,7 @@ export const ZodInterface: core.$constructor<ZodInterface> = /*@__PURE__*/ core.
 
 function _interface<T extends core.$ZodLooseShape>(
   shape: T,
-  params?: core.$ZodInterfaceParams,
+  params?: string | core.$ZodInterfaceParams,
   Class: util.Constructor<ZodInterface> = ZodInterface
 ): ZodInterface<util.CleanInterfaceShape<T>, util.InitInterfaceParams<T, {}>> {
   const cleaned = util.cached(() => util.cleanInterfaceShape(shape));
@@ -1201,7 +1203,7 @@ export { _interface as interface };
 
 export function strictInterface<T extends core.$ZodLooseShape>(
   shape: T,
-  params?: core.$ZodInterfaceParams
+  params?: string | core.$ZodInterfaceParams
 ): ZodInterface<util.CleanInterfaceShape<T>, util.InitInterfaceParams<T, {}>> {
   const cleaned = util.cached(() => util.cleanInterfaceShape(shape));
   const def: core.$ZodInterfaceDef = {
@@ -1225,7 +1227,7 @@ export function strictInterface<T extends core.$ZodLooseShape>(
 
 export function looseInterface<T extends core.$ZodLooseShape>(
   shape: T,
-  params?: core.$ZodInterfaceParams
+  params?: string | core.$ZodInterfaceParams
 ): ZodInterface<util.CleanInterfaceShape<T>, util.InitInterfaceParams<T, Record<string, unknown>>> {
   const cleaned = util.cached(() => util.cleanInterfaceShape(shape));
   const def: core.$ZodInterfaceDef = {
@@ -1348,7 +1350,7 @@ export const ZodObject: core.$constructor<ZodObject> = /*@__PURE__*/ core.$const
 });
 export function object<T extends core.$ZodShape = Record<never, core.$ZodType>>(
   shape?: T,
-  params?: core.$ZodObjectLikeParams
+  params?: string | core.$ZodObjectLikeParams
 ): ZodObject<T, {}> {
   const def: core.$ZodObjectDef = {
     type: "object",
@@ -1363,7 +1365,10 @@ export function object<T extends core.$ZodShape = Record<never, core.$ZodType>>(
 
 // strictObject
 
-export function strictObject<T extends core.$ZodShape>(shape: T, params?: core.$ZodObjectParams): ZodObject<T, {}> {
+export function strictObject<T extends core.$ZodShape>(
+  shape: T,
+  params?: string | core.$ZodObjectParams
+): ZodObject<T, {}> {
   return new ZodObject({
     type: "object",
     shape: shape as core.$ZodShape,
@@ -1379,7 +1384,7 @@ export function strictObject<T extends core.$ZodShape>(shape: T, params?: core.$
 
 export function looseObject<T extends core.$ZodShape>(
   shape: T,
-  params?: core.$ZodObjectParams
+  params?: string | core.$ZodObjectParams
 ): ZodObject<T, { [k: string]: unknown }> {
   return new ZodObject({
     type: "object",
@@ -1599,7 +1604,7 @@ export const ZodUnion: core.$constructor<ZodUnion> = /*@__PURE__*/ core.$constru
 
 export function union<const T extends readonly core.$ZodType[]>(
   options: T,
-  params?: core.$ZodUnionParams
+  params?: string | core.$ZodUnionParams
 ): ZodUnion<T> {
   return new ZodUnion({
     type: "union",
@@ -1632,11 +1637,11 @@ export interface $ZodTypeDiscriminable extends ZodType {
 export function discriminatedUnion<Types extends [$ZodTypeDiscriminable, ...$ZodTypeDiscriminable[]]>(
   disc: string,
   options: Types,
-  params?: core.$ZodDiscriminatedUnionParams
+  params?: string | core.$ZodDiscriminatedUnionParams
 ): ZodDiscriminatedUnion<Types>;
 export function discriminatedUnion<Types extends [$ZodTypeDiscriminable, ...$ZodTypeDiscriminable[]]>(
   options: Types,
-  params?: core.$ZodDiscriminatedUnionParams
+  params?: string | core.$ZodDiscriminatedUnionParams
 ): ZodDiscriminatedUnion<Types>;
 export function discriminatedUnion(...args: any[]): any {
   if (typeof args[0] === "string") args = args.slice(1);
@@ -1664,7 +1669,7 @@ export const ZodIntersection: core.$constructor<ZodIntersection> = /*@__PURE__*/
 export function intersection<T extends core.$ZodType, U extends core.$ZodType>(
   left: T,
   right: U,
-  params?: core.$ZodIntersectionParams
+  params?: string | core.$ZodIntersectionParams
 ): ZodIntersection<T, U> {
   return new ZodIntersection({
     type: "intersection",
@@ -1694,18 +1699,18 @@ export const ZodTuple: core.$constructor<ZodTuple> = /*@__PURE__*/ core.$constru
 
 export function tuple<T extends readonly [core.$ZodType, ...core.$ZodType[]]>(
   items: T,
-  params?: core.$ZodTupleParams
+  params?: string | core.$ZodTupleParams
 ): ZodTuple<T, null>;
 export function tuple<T extends readonly [core.$ZodType, ...core.$ZodType[]], Rest extends core.$ZodType>(
   items: T,
   rest: Rest,
-  params?: core.$ZodTupleParams
+  params?: string | core.$ZodTupleParams
 ): ZodTuple<T, Rest>;
-export function tuple(items: [], params?: core.$ZodTupleParams): ZodTuple<[], null>;
+export function tuple(items: [], params?: string | core.$ZodTupleParams): ZodTuple<[], null>;
 export function tuple(
   items: core.$ZodType[],
-  _paramsOrRest?: core.$ZodTupleParams | core.$ZodType,
-  _params?: core.$ZodTupleParams
+  _paramsOrRest?: string | core.$ZodTupleParams | core.$ZodType,
+  _params?: string | core.$ZodTupleParams
 ) {
   const hasRest = _paramsOrRest instanceof core.$ZodType;
   const params = hasRest ? _params : _paramsOrRest;
@@ -1738,7 +1743,7 @@ export const ZodRecord: core.$constructor<ZodRecord> = /*@__PURE__*/ core.$const
 export function record<Key extends core.$ZodRecordKey, Value extends core.$ZodType>(
   keyType: Key,
   valueType: Value,
-  params?: core.$ZodRecordParams
+  params?: string | core.$ZodRecordParams
 ): ZodRecord<Key, Value> {
   return new ZodRecord({
     type: "record",
@@ -1748,6 +1753,19 @@ export function record<Key extends core.$ZodRecordKey, Value extends core.$ZodTy
   }) as ZodRecord<Key, Value>;
 }
 
+export function partialRecord<Key extends core.$ZodRecordKey, Value extends core.$ZodType>(
+  keyType: Key,
+  valueType: Value,
+  params?: string | core.$ZodRecordParams
+): ZodRecord<ZodUnion<[Key, ZodNever]>, Value> {
+  return new ZodRecord({
+    type: "record",
+    keyType: union([keyType, never()]),
+    valueType,
+    ...util.normalizeParams(params),
+  }) as ZodRecord<ZodUnion<[Key, ZodNever]>, Value>;
+}
+
 // ZodMap
 export interface ZodMap<Key extends core.$ZodType = core.$ZodType, Value extends core.$ZodType = core.$ZodType>
   extends ZodType {
@@ -1766,7 +1784,7 @@ export const ZodMap: core.$constructor<ZodMap> = /*@__PURE__*/ core.$constructor
 export function map<Key extends core.$ZodType, Value extends core.$ZodType>(
   keyType: Key,
   valueType: Value,
-  params?: core.$ZodMapParams
+  params?: string | core.$ZodMapParams
 ): ZodMap<Key, Value> {
   return new ZodMap({
     type: "map",
@@ -1779,11 +1797,11 @@ export function map<Key extends core.$ZodType, Value extends core.$ZodType>(
 // ZodSet
 export interface ZodSet<T extends core.$ZodType = core.$ZodType> extends ZodType {
   _zod: core.$ZodSetInternals<T>;
-  min(minSize: number, params?: core.$ZodCheckMinSizeParams): this;
+  min(minSize: number, params?: string | core.$ZodCheckMinSizeParams): this;
   /** */
-  nonempty(params?: core.$ZodCheckMinSizeParams): this;
-  max(maxSize: number, params?: core.$ZodCheckMaxSizeParams): this;
-  size(size: number, params?: core.$ZodCheckSizeEqualsParams): this;
+  nonempty(params?: string | core.$ZodCheckMinSizeParams): this;
+  max(maxSize: number, params?: string | core.$ZodCheckMaxSizeParams): this;
+  size(size: number, params?: string | core.$ZodCheckSizeEqualsParams): this;
 }
 export const ZodSet: core.$constructor<ZodSet> = /*@__PURE__*/ core.$constructor("ZodSet", (inst, def) => {
   core.$ZodSet.init(inst, def);
@@ -1795,7 +1813,10 @@ export const ZodSet: core.$constructor<ZodSet> = /*@__PURE__*/ core.$constructor
   inst.size = (...args) => inst.check(core._size(...args));
 });
 
-export function set<Value extends core.$ZodType>(valueType: Value, params?: core.$ZodSetParams): ZodSet<Value> {
+export function set<Value extends core.$ZodType>(
+  valueType: Value,
+  params?: string | core.$ZodSetParams
+): ZodSet<Value> {
   return new ZodSet({
     type: "set",
     valueType,
@@ -1813,11 +1834,11 @@ export interface ZodEnum<T extends util.EnumLike = util.EnumLike> extends ZodTyp
 
   extract<const U extends readonly (keyof T)[]>(
     values: U,
-    params?: core.$ZodEnumParams
+    params?: string | core.$ZodEnumParams
   ): ZodEnum<util.Flatten<Pick<T, U[number]>>>;
   exclude<const U extends readonly (keyof T)[]>(
     values: U,
-    params?: core.$ZodEnumParams
+    params?: string | core.$ZodEnumParams
   ): ZodEnum<util.Flatten<Omit<T, U[number]>>>;
 }
 export const ZodEnum: core.$constructor<ZodEnum> = /*@__PURE__*/ core.$constructor("ZodEnum", (inst, def) => {
@@ -1862,10 +1883,10 @@ export const ZodEnum: core.$constructor<ZodEnum> = /*@__PURE__*/ core.$construct
 
 function _enum<const T extends readonly string[]>(
   values: T,
-  params?: core.$ZodEnumParams
+  params?: string | core.$ZodEnumParams
 ): ZodEnum<util.ToEnum<T[number]>>;
-function _enum<const T extends util.EnumLike>(entries: T, params?: core.$ZodEnumParams): ZodEnum<T>;
-function _enum(values: any, params?: core.$ZodEnumParams) {
+function _enum<const T extends util.EnumLike>(entries: T, params?: string | core.$ZodEnumParams): ZodEnum<T>;
+function _enum(values: any, params?: string | core.$ZodEnumParams) {
   const entries: any = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
 
   return new ZodEnum({
@@ -1883,7 +1904,7 @@ export { _enum as enum };
  * z.enum(Colors);
  * ```
  */
-export function nativeEnum<T extends util.EnumLike>(entries: T, params?: core.$ZodEnumParams): ZodEnum<T> {
+export function nativeEnum<T extends util.EnumLike>(entries: T, params?: string | core.$ZodEnumParams): ZodEnum<T> {
   return new ZodEnum({
     type: "enum",
     entries,
@@ -1904,9 +1925,12 @@ export const ZodLiteral: core.$constructor<ZodLiteral> = /*@__PURE__*/ core.$con
 
 export function literal<const T extends Array<util.Literal>>(
   value: T,
-  params?: core.$ZodLiteralParams
+  params?: string | core.$ZodLiteralParams
 ): ZodLiteral<T[number]>;
-export function literal<const T extends util.Literal>(value: T, params?: core.$ZodLiteralParams): ZodLiteral<T>;
+export function literal<const T extends util.Literal>(
+  value: T,
+  params?: string | core.$ZodLiteralParams
+): ZodLiteral<T>;
 export function literal(value: any, params: any) {
   return new ZodLiteral({
     type: "literal",
@@ -1978,7 +2002,7 @@ export const ZodTransform: core.$constructor<ZodTransform> = /*@__PURE__*/ core.
 
 export function transform<I = unknown, O = I>(
   fn: (input: I, ctx: core.ParsePayload) => O,
-  params?: core.$ZodTransformParams
+  params?: string | core.$ZodTransformParams
 ): ZodTransform<Awaited<O>, I> {
   return new ZodTransform({
     type: "transform",
@@ -2003,7 +2027,10 @@ export const ZodOptional: core.$constructor<ZodOptional> = /*@__PURE__*/ core.$c
   }
 );
 
-export function optional<T extends core.$ZodType>(innerType: T, params?: core.$ZodOptionalParams): ZodOptional<T> {
+export function optional<T extends core.$ZodType>(
+  innerType: T,
+  params?: string | core.$ZodOptionalParams
+): ZodOptional<T> {
   return new ZodOptional({
     type: "optional",
     innerType,
@@ -2027,7 +2054,10 @@ export const ZodNullable: core.$constructor<ZodNullable> = /*@__PURE__*/ core.$c
   }
 );
 
-export function nullable<T extends core.$ZodType>(innerType: T, params?: core.$ZodNullableParams): ZodNullable<T> {
+export function nullable<T extends core.$ZodType>(
+  innerType: T,
+  params?: string | core.$ZodNullableParams
+): ZodNullable<T> {
   return new ZodNullable({
     type: "nullable",
     innerType,
@@ -2059,7 +2089,7 @@ export const ZodDefault: core.$constructor<ZodDefault> = /*@__PURE__*/ core.$con
 export function _default<T extends core.$ZodType>(
   innerType: T,
   defaultValue: util.NoUndefined<core.output<T>> | (() => util.NoUndefined<core.output<T>>),
-  params?: core.$ZodDefaultParams
+  params?: string | core.$ZodDefaultParams
 ): ZodDefault<T> {
   return new ZodDefault({
     type: "default",
@@ -2087,7 +2117,7 @@ export const ZodNonOptional: core.$constructor<ZodNonOptional> = /*@__PURE__*/ c
 
 export function nonoptional<T extends core.$ZodType>(
   innerType: T,
-  params?: core.$ZodNonOptionalParams
+  params?: string | core.$ZodNonOptionalParams
 ): ZodNonOptional<T> {
   return new ZodNonOptional({
     type: "nonoptional",
@@ -2109,7 +2139,10 @@ export const ZodSuccess: core.$constructor<ZodSuccess> = /*@__PURE__*/ core.$con
   inst.unwrap = () => inst._zod.def.innerType;
 });
 
-export function success<T extends core.$ZodType>(innerType: T, params?: core.$ZodSuccessParams): ZodSuccess<T> {
+export function success<T extends core.$ZodType>(
+  innerType: T,
+  params?: string | core.$ZodSuccessParams
+): ZodSuccess<T> {
   return new ZodSuccess({
     type: "success",
     innerType,
@@ -2136,7 +2169,7 @@ export const ZodCatch: core.$constructor<ZodCatch> = /*@__PURE__*/ core.$constru
 function _catch<T extends core.$ZodType>(
   innerType: T,
   catchValue: core.output<T> | ((ctx: core.$ZodCatchCtx) => core.output<T>),
-  params?: core.$ZodCatchParams
+  params?: string | core.$ZodCatchParams
 ): ZodCatch<T> {
   return new ZodCatch({
     type: "catch",
@@ -2181,8 +2214,8 @@ export const ZodPipe: core.$constructor<ZodPipe> = /*@__PURE__*/ core.$construct
 export function pipe<
   const A extends core.$ZodType,
   B extends core.$ZodType<unknown, core.output<A>> = core.$ZodType<unknown, core.output<A>>,
->(in_: A, out: B | core.$ZodType<unknown, core.output<A>>, params?: core.$ZodPipeParams): ZodPipe<A, B>;
-export function pipe(in_: core.$ZodType, out: core.$ZodType, params?: core.$ZodPipeParams) {
+>(in_: A, out: B | core.$ZodType<unknown, core.output<A>>, params?: string | core.$ZodPipeParams): ZodPipe<A, B>;
+export function pipe(in_: core.$ZodType, out: core.$ZodType, params?: string | core.$ZodPipeParams) {
   return new ZodPipe({
     type: "pipe",
     in: in_,
@@ -2203,7 +2236,10 @@ export const ZodReadonly: core.$constructor<ZodReadonly> = /*@__PURE__*/ core.$c
   }
 );
 
-export function readonly<T extends core.$ZodType>(innerType: T, params?: core.$ZodReadonlyParams): ZodReadonly<T> {
+export function readonly<T extends core.$ZodType>(
+  innerType: T,
+  params?: string | core.$ZodReadonlyParams
+): ZodReadonly<T> {
   return new ZodReadonly({
     type: "readonly",
     innerType,
@@ -2225,7 +2261,7 @@ export const ZodTemplateLiteral: core.$constructor<ZodTemplateLiteral> = /*@__PU
 
 export function templateLiteral<const Parts extends core.$TemplateLiteralPart[]>(
   parts: Parts,
-  params?: core.$ZodTemplateLiteralParams
+  params?: string | core.$ZodTemplateLiteralParams
 ): ZodTemplateLiteral<core.$PartsToTemplateLiteral<Parts>> {
   return new ZodTemplateLiteral({
     type: "template_literal",
@@ -2267,7 +2303,10 @@ export const ZodPromise: core.$constructor<ZodPromise> = /*@__PURE__*/ core.$con
   inst.unwrap = () => inst._zod.def.innerType;
 });
 
-export function promise<T extends core.$ZodType>(innerType: T, params?: core.$ZodPromiseParams): ZodPromise<T> {
+export function promise<T extends core.$ZodType>(
+  innerType: T,
+  params?: string | core.$ZodPromiseParams
+): ZodPromise<T> {
   return new ZodPromise({
     type: "promise",
     innerType,
@@ -2285,7 +2324,7 @@ export const ZodCustom: core.$constructor<ZodCustom> = /*@__PURE__*/ core.$const
 });
 
 // custom checks
-export function check<O = unknown>(fn: core.CheckFn<O>, params?: core.$ZodCustomParams): core.$ZodCheck<O> {
+export function check<O = unknown>(fn: core.CheckFn<O>, params?: string | core.$ZodCustomParams): core.$ZodCheck<O> {
   const ch = new core.$ZodCheck({
     check: "custom",
     ...util.normalizeParams(params),
@@ -2312,7 +2351,7 @@ export function refine<T>(
 // superRefine
 export function superRefine<T>(
   fn: (arg: T, payload: RefinementCtx<T>) => void | Promise<void>,
-  params?: core.$ZodCustomParams
+  params?: string | core.$ZodCustomParams
 ): core.$ZodCheck<T> {
   const ch = check<T>((payload) => {
     (payload as RefinementCtx).addIssue = (issue) => {
@@ -2361,7 +2400,7 @@ function _instanceof<T extends typeof Class>(
 export { _instanceof as instanceof };
 
 // stringbool
-export const stringbool: (_params?: core.$ZodStringBoolParams) => ZodPipe<ZodUnknown, ZodBoolean> =
+export const stringbool: (_params?: string | core.$ZodStringBoolParams) => ZodPipe<ZodUnknown, ZodBoolean> =
   /*@__PURE__*/ core._stringbool.bind(null, {
     Pipe: ZodPipe,
     Boolean: ZodBoolean,
@@ -2378,7 +2417,7 @@ export type ZodJSONSchema = ZodUnion<
   };
 };
 
-export function json(params?: core.$ZodCustomParams): ZodJSONSchema {
+export function json(params?: string | core.$ZodCustomParams): ZodJSONSchema {
   const jsonSchema: any = lazy(() => {
     return union([string(params), number(), boolean(), _null(), array(jsonSchema), record(string(), jsonSchema)]);
   });
diff --git a/play.ts b/play.ts
index a3931f9f..8e4f9670 100644
--- a/play.ts
+++ b/play.ts
@@ -1,10 +1,2 @@
 import * as z from "zod";
-const name = z.object({ first: z.string(), last: z.string() }).meta({ id: "name" });
-const result = z.toJSONSchema(
-  z.object({
-    name: name,
-    alt: name.readonly(),
-    age: z.number().meta({ id: "age" }),
-  })
-);
-console.dir(result, { depth: null });
+z;


Test patch:
diff --git a/packages/zod/tests/error.test.ts b/packages/zod/tests/error.test.ts
index 2b92b7e8..2019bd9b 100644
--- a/packages/zod/tests/error.test.ts
+++ b/packages/zod/tests/error.test.ts
@@ -651,3 +651,35 @@ test("dont short circuit on continuable errors", () => {
   `);
   // expect(result.error!.issues.length).toEqual(2);
 });
+
+test("string error params", () => {
+  const a = z.string("Bad!");
+  expect(a.safeParse(123).error!.issues[0].message).toBe("Bad!");
+
+  const b = z.string().min(5, "Too short!");
+  expect(b.safeParse("abc").error!.issues[0].message).toBe("Too short!");
+
+  const c = z.uuid("Bad UUID!");
+  expect(c.safeParse("not-a-uuid").error!.issues[0].message).toBe("Bad UUID!");
+
+  const d = z.string().datetime({ message: "Bad date!" });
+  expect(d.safeParse("not-a-date").error!.issues[0].message).toBe("Bad date!");
+
+  const e = z.array(z.string(), "Bad array!");
+  expect(e.safeParse("not-an-array").error!.issues[0].message).toBe("Bad array!");
+
+  const f = z.array(z.string()).min(5, "Too few items!");
+  expect(f.safeParse(["a", "b"]).error!.issues[0].message).toBe("Too few items!");
+
+  const g = z.set(z.string(), "Bad set!");
+  expect(g.safeParse("not-a-set").error!.issues[0].message).toBe("Bad set!");
+
+  const h = z.array(z.string(), "Bad array!");
+  expect(h.safeParse(123).error!.issues[0].message).toBe("Bad array!");
+
+  const i = z.set(z.string(), "Bad set!");
+  expect(i.safeParse(123).error!.issues[0].message).toBe("Bad set!");
+
+  const j = z.array(z.string(), "Bad array!");
+  expect(j.safeParse(null).error!.issues[0].message).toBe("Bad array!");
+});
diff --git a/packages/zod/tests/json-schema.test.ts b/packages/zod/tests/json-schema.test.ts
index f44519cc..77ceb0e8 100644
--- a/packages/zod/tests/json-schema.test.ts
+++ b/packages/zod/tests/json-schema.test.ts
@@ -157,29 +157,29 @@ describe("toJSONSchema", () => {
     `);
     expect(toJSONSchema(z.int())).toMatchInlineSnapshot(`
       {
-        "exclusiveMaximum": 9007199254740991,
-        "exclusiveMinimum": -9007199254740991,
+        "maximum": 9007199254740991,
+        "minimum": -9007199254740991,
         "type": "integer",
       }
     `);
     expect(toJSONSchema(z.int32())).toMatchInlineSnapshot(`
       {
-        "exclusiveMaximum": 2147483647,
-        "exclusiveMinimum": -2147483648,
+        "maximum": 2147483647,
+        "minimum": -2147483648,
         "type": "integer",
       }
     `);
     expect(toJSONSchema(z.float32())).toMatchInlineSnapshot(`
       {
-        "exclusiveMaximum": 3.4028234663852886e+38,
-        "exclusiveMinimum": -3.4028234663852886e+38,
+        "maximum": 3.4028234663852886e+38,
+        "minimum": -3.4028234663852886e+38,
         "type": "number",
       }
     `);
     expect(toJSONSchema(z.float64())).toMatchInlineSnapshot(`
       {
-        "exclusiveMaximum": 1.7976931348623157e+308,
-        "exclusiveMinimum": -1.7976931348623157e+308,
+        "maximum": 1.7976931348623157e+308,
+        "minimum": -1.7976931348623157e+308,
         "type": "number",
       }
     `);
@@ -346,7 +346,7 @@ describe("toJSONSchema", () => {
     const schema = z.union([z.string(), z.number()]);
     expect(toJSONSchema(schema)).toMatchInlineSnapshot(`
       {
-        "oneOf": [
+        "anyOf": [
           {
             "type": "string",
           },
@@ -875,7 +875,7 @@ test("pipe", () => {
   `);
   // => { type: "number" }
 
-  const b = z.toJSONSchema(mySchema, { type: "input" });
+  const b = z.toJSONSchema(mySchema, { io: "input" });
   expect(b).toMatchInlineSnapshot(`
     {
       "type": "string",
@@ -979,7 +979,7 @@ test("extract schemas with id", () => {
           "$ref": "#/$defs/name",
         },
         "last_name": {
-          "oneOf": [
+          "anyOf": [
             {
               "$ref": "#/$defs/name",
             },
@@ -1003,15 +1003,29 @@ test("extract schemas with id", () => {
 });
 
 test("unrepresentable literal values are ignored", () => {
-  const schema = z.literal(["hello", null, 5, BigInt(1324), undefined]);
-  const result = z.toJSONSchema(schema, { unrepresentable: "any" });
-  expect(result).toMatchInlineSnapshot(`
+  const a = z.toJSONSchema(z.literal(["hello", null, 5, BigInt(1324), undefined]), { unrepresentable: "any" });
+  expect(a).toMatchInlineSnapshot(`
     {
       "enum": [
         "hello",
         null,
         5,
+        1324,
+      ],
+    }
+  `);
+
+  const b = z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), { unrepresentable: "any" });
+  expect(b).toMatchInlineSnapshot(`
+    {
+      "enum": [
+        null,
+        5,
+        1324,
       ],
     }
   `);
+
+  const c = z.toJSONSchema(z.literal([undefined]), { unrepresentable: "any" });
+  expect(c).toMatchInlineSnapshot(`{}`);
 });
diff --git a/packages/zod/tests/lazy.test.ts b/packages/zod/tests/lazy.test.ts
new file mode 100644
index 00000000..457a53a4
--- /dev/null
+++ b/packages/zod/tests/lazy.test.ts
@@ -0,0 +1,46 @@
+import { expect, expectTypeOf, test } from "vitest";
+
+import * as z from "zod";
+
+test("qin qout passthrough", () => {
+  const object = z.object({
+    a: z.lazy(() => z.string()),
+    b: z.lazy(() => z.string().optional()),
+    c: z.lazy(() => z.string().default("default")),
+  });
+
+  type ObjectTypeIn = z.input<typeof object>;
+  expectTypeOf<ObjectTypeIn>().toEqualTypeOf<{
+    a: string;
+    b?: string | undefined;
+    c?: string | undefined;
+  }>();
+
+  type ObjectTypeOut = z.output<typeof object>;
+  expectTypeOf<ObjectTypeOut>().toEqualTypeOf<{
+    a: string;
+    b?: string | undefined;
+    c: string;
+  }>();
+
+  const result = object.parse(
+    {
+      a: "hello",
+      b: undefined,
+    },
+    { noPrecompilation: true }
+  );
+  expect(result).toEqual({
+    a: "hello",
+    // b: undefined,
+    c: "default",
+  });
+
+  const lazyString = z.lazy(() => z.string().optional());
+  expect(lazyString._zod.qout).toEqual("true");
+  expect(object._zod.def.optional).toMatchInlineSnapshot(`
+    [
+      "b",
+    ]
+  `);
+});
diff --git a/packages/zod/tests/string-formats.test.ts b/packages/zod/tests/string-formats.test.ts
new file mode 100644
index 00000000..c664d4af
--- /dev/null
+++ b/packages/zod/tests/string-formats.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "vitest";
+
+import * as z from "zod";
+
+test("string format methods", () => {
+  const a = z.email().min(10);
+  const b = z.email().max(10);
+  const c = z.email().length(10);
+  const d = z.email().uppercase();
+  const e = z.email().lowercase();
+
+  // Positive and negative cases for `a`
+  expect(a.safeParse("longemail@example.com").success).toBe(true); // Positive
+  expect(a.safeParse("ort@e.co").success).toBe(false); // Negative
+
+  // Positive and negative cases for `b`
+  expect(b.safeParse("sho@e.co").success).toBe(true); // Positive
+  expect(b.safeParse("longemail@example.com").success).toBe(false); // Negative
+
+  // Positive and negative cases for `c`
+  expect(c.safeParse("56780@e.co").success).toBe(true); // Positive
+  expect(c.safeParse("shoasdfasdfrt@e.co").success).toBe(false); // Negative
+
+  // Positive and negative cases for `d`
+  expect(d.safeParse("EMAIL@EXAMPLE.COM").success).toBe(true); // Positive
+  expect(d.safeParse("email@example.com").success).toBe(false); // Negative
+
+  // Positive and negative cases for `e`
+  expect(e.safeParse("email@example.com").success).toBe(true); // Positive
+  expect(e.safeParse("EMAIL@EXAMPLE.COM").success).toBe(false); // Negative
+});