mirror of
https://github.com/hanzoai/zod.git
synced 2026-09-24 17:04:49 +00:00
v4.0.6 (#4941)
* Fix File reference issues. Fixes #4880. Fixes #4933. * Relax metadata constraint to object. Fixes #4929 * Clean up partialRecord implementation. Closes #4882. * Fixes catch handling in object schemas. Fixes #4883. * Add treeshake case * Document optionality changes * Fix docs build * Do not return empty metadata objects. Fixed #4885 * Escape non-string literal patterns. Closes #4894 * Surface continuable errors preferentially in unions. Fixes #4896 * Drop private modifier. Fixes #4903 * Simplify object parsing. Closes #4900 * Clean up .caught * Make message a non-getter. Closes #4901. * Standardize behavior of transform and preprocess. Closes #4910 * Clean up docs * Fix extend for recursive objects. Fixes #4931 * Make object methods work with recursive types. Closes #4931 again * Improve laziness of object methods * Make standardschema FailureResult work with prettifyError. Closes #4914. * Do not use normalized URLs by default. Add normalize flag to ZodURL. Closes #4906 * Tweak URL docs. Closes #4959 * 4.0.6
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": ["es6", "dom"],
|
||||
"lib": ["es6", "es2020"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"jsx": "react",
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
description: Testing guidelines for the Zod codebase - when to use play.ts vs test suite, TypeScript requirements, and testing patterns
|
||||
globs: *.test.ts,*.test.tsx,play.ts
|
||||
---
|
||||
|
||||
# Zod Testing Guidelines
|
||||
|
||||
## Always Use TypeScript
|
||||
- **All tests must be written in TypeScript** - never use JavaScript
|
||||
- Test files should use `.test.ts` extension
|
||||
- Import types properly: `import type { ... }`
|
||||
|
||||
## When to Use play.ts vs Test Suite
|
||||
|
||||
### Use [play.ts](mdc:play.ts) for:
|
||||
- Quick experimentation and prototyping
|
||||
- Manual testing of new features during development
|
||||
- Debugging specific behaviors
|
||||
- One-off validation of edge cases
|
||||
- Temporary verification before writing formal tests
|
||||
|
||||
### Use Test Suite for:
|
||||
- **All permanent test cases** - features must have proper tests
|
||||
- Regression testing
|
||||
- API validation
|
||||
- Edge case coverage
|
||||
- Performance benchmarks
|
||||
|
||||
## Test Organization
|
||||
|
||||
### Test File Structure
|
||||
Tests are located in:
|
||||
- `packages/zod/src/v4/classic/tests/` - V4 classic API tests
|
||||
- `packages/zod/src/v4/core/tests/` - V4 core tests
|
||||
- `packages/zod/src/v3/tests/` - V3 compatibility tests
|
||||
|
||||
### Test File Naming
|
||||
- Use descriptive names: `string.test.ts`, `object.test.ts`, `url-validation.test.ts`
|
||||
- Group related functionality in the same test file
|
||||
- Follow existing patterns in [packages/zod/src/v4/classic/tests/string.test.ts](mdc:packages/zod/src/v4/classic/tests/string.test.ts)
|
||||
|
||||
## Test Writing Patterns
|
||||
|
||||
### Framework Usage
|
||||
- Use **Vitest** as the test framework
|
||||
- Import: `import { expect, test } from "vitest";`
|
||||
- Import Zod: `import * as z from "zod/v4";`
|
||||
|
||||
### Test Structure
|
||||
```typescript
|
||||
test("descriptive test name", () => {
|
||||
const schema = z.string().url();
|
||||
|
||||
// Test valid cases
|
||||
expect(schema.parse("https://example.com")).toBe("https://example.com");
|
||||
|
||||
// Test invalid cases
|
||||
expect(() => schema.parse("invalid")).toThrow();
|
||||
});
|
||||
```
|
||||
|
||||
### Testing Preferences
|
||||
- **Keep test suites concise** - use as few test cases as needed to cover functionality
|
||||
- **Don't skip tests** due to type issues - fix the types instead
|
||||
- **Don't oversimplify tests** - maintain adequate coverage
|
||||
- Test both success and failure cases
|
||||
- Use descriptive test names that explain the behavior being tested
|
||||
|
||||
### URL Testing Example
|
||||
When testing URL validation (as seen in recent work):
|
||||
```typescript
|
||||
test("url preserves original input", () => {
|
||||
const url = z.string().url();
|
||||
const input = "https://example.com?key=value";
|
||||
expect(url.parse(input)).toBe(input);
|
||||
});
|
||||
|
||||
test("url normalize flag", () => {
|
||||
const normalizeUrl = z.url({ normalize: true });
|
||||
expect(normalizeUrl.parse("https://example.com?key=value"))
|
||||
.toBe("https://example.com/?key=value");
|
||||
});
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Run Specific Tests
|
||||
```bash
|
||||
pnpm vitest run src/v4/classic/tests/string.test.ts -t "url"
|
||||
```
|
||||
|
||||
### Run All Tests
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### Build Before Testing
|
||||
Always rebuild when testing core changes:
|
||||
```bash
|
||||
pnpm build && pnpm test
|
||||
```
|
||||
|
||||
## Test Development Workflow
|
||||
|
||||
1. **Start with [play.ts](mdc:play.ts)** for initial exploration
|
||||
2. **Write proper tests** once behavior is confirmed
|
||||
3. **Test edge cases** and error conditions
|
||||
4. **Verify backward compatibility**
|
||||
5. **Run full test suite** before committing
|
||||
|
||||
Remember: Features without tests are incomplete. Every new feature or bug fix should include corresponding test coverage.
|
||||
+2
-2
@@ -18,9 +18,7 @@
|
||||
"@arethetypeswrong/cli": "^0.17.4",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/node": "^20.17.30",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@web-std/file": "^3.0.3",
|
||||
"arktype": "^2.1.19",
|
||||
"benchmark": "^2.1.4",
|
||||
"chalk": "^5.4.1",
|
||||
@@ -34,6 +32,8 @@
|
||||
"mitata": "^0.1.14",
|
||||
"prettier": "^3.5.3",
|
||||
"recheck": "^4.5.0",
|
||||
"@web-std/file": "^3.0.3",
|
||||
|
||||
"rolldown": "1.0.0-beta.18",
|
||||
"rollup": "^4.39.0",
|
||||
"semver": "^7.7.2",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { metabench } from "./metabench.js";
|
||||
|
||||
import * as z3 from "zod/v3";
|
||||
import * as z4 from "zod/v4";
|
||||
import * as z4lib from "zodnext/v4";
|
||||
import * as z4lib from "zod4/v4";
|
||||
|
||||
|
||||
const z3fields ={
|
||||
|
||||
@@ -48,12 +48,12 @@ export default async function Page(props: {
|
||||
// you can add other MDX components here
|
||||
blockquote: Callout,
|
||||
Tabs,
|
||||
h1: (props) => <Heading as="h1" {...props} />,
|
||||
h2: (props) => <Heading as="h2" {...props} />,
|
||||
h3: (props) => <Heading as="h3" {...props} />,
|
||||
h4: (props) => <Heading as="h4" {...props} />,
|
||||
h5: (props) => <Heading as="h5" {...props} />,
|
||||
h6: (props) => <Heading as="h6" {...props} />,
|
||||
h1: (props: any) => <Heading as="h1" {...props} />,
|
||||
h2: (props: any) => <Heading as="h2" {...props} />,
|
||||
h3: (props: any) => <Heading as="h3" {...props} />,
|
||||
h4: (props: any) => <Heading as="h4" {...props} />,
|
||||
h5: (props: any) => <Heading as="h5" {...props} />,
|
||||
h6: (props: any) => <Heading as="h6" {...props} />,
|
||||
}}
|
||||
/>
|
||||
</DocsBody>
|
||||
|
||||
@@ -317,7 +317,6 @@ const schema = z.url();
|
||||
schema.parse("https://example.com"); // ✅
|
||||
schema.parse("http://localhost"); // ✅
|
||||
schema.parse("mailto:[email protected]"); // ✅
|
||||
schema.parse("sup"); // ❌
|
||||
```
|
||||
|
||||
As you can see this is quite permissive. Internally this uses the `new URL()` constructor to validate inputs; this behavior may differ across platforms and runtimes but it's the mostly rigorous way to validate URIs/URLs on any given JS runtime/engine.
|
||||
@@ -357,6 +356,13 @@ schema.parse("http://example.com"); // ❌
|
||||
```
|
||||
</Callout>
|
||||
|
||||
To normalize URLs, use the `normalize` flag. This will overwrite the input value with the [normalized URL](https://chatgpt.com/share/6881547f-bebc-800f-9093-f5981e277c2c) returned by `new URL()`.
|
||||
|
||||
```ts
|
||||
new URL("HTTP://ExAmPle.com:80/./a/../b?X=1#f oo").href
|
||||
// => "http://example.com/b?X=1#f%20oo"
|
||||
```
|
||||
|
||||
### ISO datetimes
|
||||
|
||||
As you may have noticed, Zod string includes a few date/time related validations. These validations are regular expression based, so they are not as strict as a full date/time library. However, they are very convenient for validating user input.
|
||||
|
||||
@@ -74,7 +74,6 @@ All other string formats are supported via `pattern`:
|
||||
```ts
|
||||
z.base64url();
|
||||
z.cuid();
|
||||
z.regex();
|
||||
z.emoji();
|
||||
z.nanoid();
|
||||
z.cuid2();
|
||||
|
||||
@@ -409,6 +409,20 @@ schema.parse(undefined); // => 4
|
||||
|
||||
## `z.object()`
|
||||
|
||||
### defaults applied within optional fields
|
||||
|
||||
Defaults inside your properties are applied, even within optional fields. This aligns better with expectations and resolves a long-standing usability issue with Zod 3. This is a subtle change that may cause breakage in code paths that rely on key existence, etc.
|
||||
|
||||
```ts
|
||||
const schema = z.object({
|
||||
a: z.string().default("tuna").optional(),
|
||||
});
|
||||
|
||||
schema.parse({});
|
||||
// Zod 4: { a: "tuna" }
|
||||
// Zod 3: {}
|
||||
```
|
||||
|
||||
### deprecates `.strict()` and `.passthrough()`
|
||||
|
||||
These methods are generally no longer necessary. Instead use the top-level `z.strictObject()` and `z.looseObject()` functions.
|
||||
|
||||
@@ -7,6 +7,23 @@ export const config = {
|
||||
runtime: "edge",
|
||||
};
|
||||
|
||||
declare module 'react' {
|
||||
interface HTMLAttributes<T> {
|
||||
/**
|
||||
* Specify styles using Tailwind CSS classes. This feature is currently experimental.
|
||||
* If `style` prop is also specified, styles generated with `tw` prop will be overridden.
|
||||
*
|
||||
* Example:
|
||||
* - `tw='w-full h-full bg-blue-200'`
|
||||
* - `tw='text-9xl'`
|
||||
* - `tw='text-[80px]'`
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
tw?: string;
|
||||
}
|
||||
}
|
||||
|
||||
// export const loadImage = (img: string) => {
|
||||
// const p = path.join(process.cwd(), img);
|
||||
// const image = fs.readFileSync(p);
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
],
|
||||
"types": ["mdx", "node", "react", "react-dom", "@vercel/og"]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "components/if.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as v from "valibot"; // 1.37 kB
|
||||
|
||||
const Valibot = v.object({
|
||||
email: v.pipe(
|
||||
v.string(),
|
||||
v.nonEmpty("Please enter your email."),
|
||||
v.email("The email address is badly formatted.")
|
||||
),
|
||||
password: v.pipe(
|
||||
v.string(),
|
||||
v.nonEmpty("Please enter your password."),
|
||||
v.minLength(8, "Your password must have 8 characters or more.")
|
||||
),
|
||||
});
|
||||
|
||||
v.parse(Valibot, {});
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as z from "zod/mini"; // 6.57 kB
|
||||
|
||||
const Zod = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.check(
|
||||
z.minLength(1, "Please enter your email."),
|
||||
z.email("The email address is badly formatted.")
|
||||
),
|
||||
password: z.string()
|
||||
.check(
|
||||
z.minLength(1, "Please enter your password."),
|
||||
z.minLength(8, "Your password must have 8 characters or more.")
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
Zod.parse({});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zod/zod",
|
||||
"version": "4.0.5",
|
||||
"version": "4.0.6",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zod",
|
||||
"version": "4.0.5",
|
||||
"version": "4.0.6",
|
||||
"type": "module",
|
||||
"author": "Colin McDonnell <[email protected]>",
|
||||
"description": "TypeScript-first schema declaration and validation library with static type inference",
|
||||
|
||||
@@ -705,6 +705,7 @@ function isValidJWT(jwt: string, alg?: string): boolean {
|
||||
.replace(/-/g, "+")
|
||||
.replace(/_/g, "/")
|
||||
.padEnd(header.length + ((4 - (header.length % 4)) % 4), "=");
|
||||
// @ts-ignore
|
||||
const decoded = JSON.parse(atob(base64));
|
||||
if (typeof decoded !== "object" || decoded === null) return false;
|
||||
if ("typ" in decoded && decoded?.typ !== "JWT") return false;
|
||||
@@ -875,6 +876,7 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
|
||||
}
|
||||
} else if (check.kind === "url") {
|
||||
try {
|
||||
// @ts-ignore
|
||||
new URL(input.data);
|
||||
} catch {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
@@ -2454,7 +2456,7 @@ export class ZodObject<
|
||||
Output = objectOutputType<T, Catchall, UnknownKeys>,
|
||||
Input = objectInputType<T, Catchall, UnknownKeys>,
|
||||
> extends ZodType<Output, ZodObjectDef<T, UnknownKeys, Catchall>, Input> {
|
||||
private _cached: { shape: T; keys: string[] } | null = null;
|
||||
_cached: { shape: T; keys: string[] } | null = null;
|
||||
|
||||
_getCached(): { shape: T; keys: string[] } {
|
||||
if (this._cached !== null) return this._cached;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as core from "../core/index.js";
|
||||
import { $ZodError } from "../core/index.js";
|
||||
import * as util from "../core/util.js";
|
||||
|
||||
/** @deprecated Use `z.core.$ZodIssue` from `@zod/core` instead, especially if you are building a library on top of Zod. */
|
||||
export type ZodIssue = core.$ZodIssue;
|
||||
@@ -34,11 +35,17 @@ const initializer = (inst: ZodError, issues: core.$ZodIssue[]) => {
|
||||
// enumerable: false,
|
||||
},
|
||||
addIssue: {
|
||||
value: (issue: any) => inst.issues.push(issue),
|
||||
value: (issue: any) => {
|
||||
inst.issues.push(issue);
|
||||
inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
|
||||
},
|
||||
// enumerable: false,
|
||||
},
|
||||
addIssues: {
|
||||
value: (issues: any) => inst.issues.push(...issues),
|
||||
value: (issues: any) => {
|
||||
inst.issues.push(...issues);
|
||||
inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
|
||||
},
|
||||
// enumerable: false,
|
||||
},
|
||||
isEmpty: {
|
||||
|
||||
@@ -1068,9 +1068,7 @@ export interface ZodObject<
|
||||
/** This is the default behavior. This method call is likely unnecessary. */
|
||||
strip(): ZodObject<Shape, core.$strip>;
|
||||
|
||||
extend<U extends core.$ZodLooseShape & Partial<Record<keyof Shape, core.SomeType>>>(
|
||||
shape: U
|
||||
): ZodObject<util.Extend<Shape, U>, Config>;
|
||||
extend<U extends core.$ZodLooseShape>(shape: U): ZodObject<util.Extend<Shape, U>, Config>;
|
||||
|
||||
/**
|
||||
* @deprecated Use [`A.extend(B.shape)`](https://zod.dev/api?id=extend) instead.
|
||||
@@ -1130,7 +1128,6 @@ export const ZodObject: core.$constructor<ZodObject> = /*@__PURE__*/ core.$const
|
||||
inst.keyof = () => _enum(Object.keys(inst._zod.def.shape)) as any;
|
||||
inst.catchall = (catchall) => inst.clone({ ...inst._zod.def, catchall: catchall as any as core.$ZodType }) as any;
|
||||
inst.passthrough = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
|
||||
// inst.nonstrict = () => inst.clone({ ...inst._zod.def, catchall: api.unknown() });
|
||||
inst.loose = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
|
||||
inst.strict = () => inst.clone({ ...inst._zod.def, catchall: never() });
|
||||
inst.strip = () => inst.clone({ ...inst._zod.def, catchall: undefined });
|
||||
@@ -1350,9 +1347,11 @@ export function partialRecord<Key extends core.$ZodRecordKey, Value extends core
|
||||
valueType: Value,
|
||||
params?: string | core.$ZodRecordParams
|
||||
): ZodRecord<Key & core.$partial, Value> {
|
||||
const k = core.clone(keyType);
|
||||
k._zod.values = undefined;
|
||||
return new ZodRecord({
|
||||
type: "record",
|
||||
keyType: union([keyType, never()]),
|
||||
keyType: k,
|
||||
valueType: valueType as any,
|
||||
...util.normalizeParams(params),
|
||||
}) as any;
|
||||
@@ -1584,7 +1583,7 @@ export const ZodTransform: core.$constructor<ZodTransform> = /*@__PURE__*/ core.
|
||||
_issue.code ??= "custom";
|
||||
_issue.input ??= payload.value;
|
||||
_issue.inst ??= inst;
|
||||
_issue.continue ??= true;
|
||||
// _issue.continue ??= true;
|
||||
payload.issues.push(util.issue(_issue));
|
||||
}
|
||||
};
|
||||
@@ -1838,12 +1837,16 @@ export function pipe(in_: core.SomeType, out: core.SomeType) {
|
||||
// ZodReadonly
|
||||
export interface ZodReadonly<T extends core.SomeType = core.$ZodType>
|
||||
extends _ZodType<core.$ZodReadonlyInternals<T>>,
|
||||
core.$ZodReadonly<T> {}
|
||||
core.$ZodReadonly<T> {
|
||||
unwrap(): T;
|
||||
}
|
||||
export const ZodReadonly: core.$constructor<ZodReadonly> = /*@__PURE__*/ core.$constructor(
|
||||
"ZodReadonly",
|
||||
(inst, def) => {
|
||||
core.$ZodReadonly.init(inst, def);
|
||||
ZodType.init(inst, def);
|
||||
|
||||
inst.unwrap = () => inst._zod.def.innerType;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -550,3 +550,46 @@ test("disc union treeify/format", () => {
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("update message after adding issues", () => {
|
||||
const e = new z.ZodError([]);
|
||||
e.addIssue({
|
||||
code: "custom",
|
||||
message: "message",
|
||||
input: "asdf",
|
||||
path: [],
|
||||
});
|
||||
expect(e.message).toMatchInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "message",
|
||||
"input": "asdf",
|
||||
"path": []
|
||||
}
|
||||
]"
|
||||
`);
|
||||
|
||||
e.addIssue({
|
||||
code: "custom",
|
||||
message: "message",
|
||||
input: "asdf",
|
||||
path: [],
|
||||
});
|
||||
expect(e.message).toMatchInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "message",
|
||||
"input": "asdf",
|
||||
"path": []
|
||||
},
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "message",
|
||||
"input": "asdf",
|
||||
"path": []
|
||||
}
|
||||
]"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -145,3 +145,196 @@ test("partial with mask -- ignore falsy values", async () => {
|
||||
masked.parse({ country: "US" });
|
||||
await masked.parseAsync({ country: "US" });
|
||||
});
|
||||
|
||||
test("catch/prefault/default", () => {
|
||||
const mySchema = z.object({
|
||||
a: z.string().catch("catch value").optional(),
|
||||
b: z.string().default("default value").optional(),
|
||||
c: z.string().prefault("prefault value").optional(),
|
||||
d: z.string().catch("catch value"),
|
||||
e: z.string().default("default value"),
|
||||
f: z.string().prefault("prefault value"),
|
||||
});
|
||||
|
||||
expect(mySchema.parse({})).toMatchInlineSnapshot(`
|
||||
{
|
||||
"b": "default value",
|
||||
"c": "prefault value",
|
||||
"d": "catch value",
|
||||
"e": "default value",
|
||||
"f": "prefault value",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(mySchema.parse({}, { jitless: true })).toMatchInlineSnapshot(`
|
||||
{
|
||||
"b": "default value",
|
||||
"c": "prefault value",
|
||||
"d": "catch value",
|
||||
"e": "default value",
|
||||
"f": "prefault value",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("handleOptionalObjectResult branches", () => {
|
||||
const mySchema = z.object({
|
||||
// Branch: input[key] === undefined, key not in input, caught error
|
||||
caughtMissing: z.string().catch("caught").optional(),
|
||||
// Branch: input[key] === undefined, key in input, caught error
|
||||
caughtUndefined: z.string().catch("caught").optional(),
|
||||
// Branch: input[key] === undefined, key not in input, validation issues
|
||||
issueMissing: z.string().min(5).optional(),
|
||||
// Branch: input[key] === undefined, key in input, validation issues
|
||||
issueUndefined: z.string().min(5).optional(),
|
||||
// Branch: input[key] === undefined, validation returns undefined
|
||||
validUndefined: z.string().optional(),
|
||||
// Branch: input[key] === undefined, non-undefined result (default/transform)
|
||||
defaultValue: z.string().default("default").optional(),
|
||||
// Branch: input[key] defined, caught error
|
||||
caughtDefined: z.string().catch("caught").optional(),
|
||||
// Branch: input[key] defined, validation issues
|
||||
issueDefined: z.string().min(5).optional(),
|
||||
// Branch: input[key] defined, validation returns undefined
|
||||
validDefinedUndefined: z
|
||||
.string()
|
||||
.transform(() => undefined)
|
||||
.optional(),
|
||||
// Branch: input[key] defined, non-undefined value
|
||||
validDefined: z.string().optional(),
|
||||
});
|
||||
|
||||
// Test input[key] === undefined cases
|
||||
const result1 = mySchema.parse(
|
||||
{
|
||||
// caughtMissing: not present (key not in input)
|
||||
caughtUndefined: undefined, // key in input
|
||||
// issueMissing: not present (key not in input)
|
||||
issueUndefined: undefined, // key in input
|
||||
validUndefined: undefined,
|
||||
// defaultValue: not present, will get default
|
||||
},
|
||||
{ jitless: true }
|
||||
);
|
||||
|
||||
expect(result1).toEqual({
|
||||
caughtUndefined: undefined,
|
||||
issueUndefined: undefined,
|
||||
validUndefined: undefined,
|
||||
defaultValue: "default",
|
||||
});
|
||||
|
||||
// Test input[key] defined cases (successful)
|
||||
const result2 = mySchema.parse(
|
||||
{
|
||||
caughtDefined: 123, // invalid type, should catch
|
||||
validDefinedUndefined: "test", // transforms to undefined
|
||||
validDefined: "valid", // valid value
|
||||
},
|
||||
{ jitless: true }
|
||||
);
|
||||
|
||||
expect(result2).toEqual({
|
||||
caughtDefined: "caught",
|
||||
validDefinedUndefined: undefined,
|
||||
validDefined: "valid",
|
||||
defaultValue: "default",
|
||||
});
|
||||
|
||||
// Test validation issues are properly reported (input[key] defined, validation fails)
|
||||
expect(() =>
|
||||
mySchema.parse(
|
||||
{
|
||||
issueDefined: "abc", // too short
|
||||
},
|
||||
{ jitless: true }
|
||||
)
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("fastpass vs non-fastpass consistency", () => {
|
||||
const mySchema = z.object({
|
||||
caughtMissing: z.string().catch("caught").optional(),
|
||||
caughtUndefined: z.string().catch("caught").optional(),
|
||||
issueMissing: z.string().min(5).optional(),
|
||||
issueUndefined: z.string().min(5).optional(),
|
||||
validUndefined: z.string().optional(),
|
||||
defaultValue: z.string().default("default").optional(),
|
||||
caughtDefined: z.string().catch("caught").optional(),
|
||||
validDefinedUndefined: z
|
||||
.string()
|
||||
.transform(() => undefined)
|
||||
.optional(),
|
||||
validDefined: z.string().optional(),
|
||||
});
|
||||
|
||||
const input = {
|
||||
caughtUndefined: undefined,
|
||||
issueUndefined: undefined,
|
||||
validUndefined: undefined,
|
||||
caughtDefined: 123,
|
||||
validDefinedUndefined: "test",
|
||||
validDefined: "valid",
|
||||
};
|
||||
|
||||
// Test both paths produce identical results
|
||||
const jitlessResult = mySchema.parse(input, { jitless: true });
|
||||
const fastpassResult = mySchema.parse(input);
|
||||
|
||||
expect(jitlessResult).toEqual(fastpassResult);
|
||||
expect(jitlessResult).toEqual({
|
||||
caughtUndefined: undefined,
|
||||
issueUndefined: undefined,
|
||||
validUndefined: undefined,
|
||||
defaultValue: "default",
|
||||
caughtDefined: "caught",
|
||||
validDefinedUndefined: undefined,
|
||||
validDefined: "valid",
|
||||
});
|
||||
});
|
||||
|
||||
test("optional with check", () => {
|
||||
const baseSchema = z
|
||||
.string()
|
||||
.optional()
|
||||
.check(({ value, ...ctx }) => {
|
||||
ctx.issues.push({
|
||||
code: "custom",
|
||||
input: value,
|
||||
message: "message",
|
||||
});
|
||||
});
|
||||
|
||||
// this correctly fails
|
||||
expect(baseSchema.safeParse(undefined)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "message",
|
||||
"path": []
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
}
|
||||
`);
|
||||
|
||||
const schemaObject = z.object({
|
||||
date: baseSchema,
|
||||
});
|
||||
|
||||
expect(schemaObject.safeParse({ date: undefined })).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "message",
|
||||
"path": [
|
||||
"date"
|
||||
]
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -114,14 +114,14 @@ test("pick/omit/required/partial - do not allow unknown keys", () => {
|
||||
age: z.number(),
|
||||
});
|
||||
|
||||
expect(() => schema.pick({ name: true, asdf: true })).toThrow();
|
||||
expect(() => schema.pick({ name: true, asdf: true }).safeParse({})).toThrow();
|
||||
|
||||
// @ts-expect-error
|
||||
expect(() => schema.pick({ $unknown: true })).toThrow();
|
||||
expect(() => schema.pick({ $unknown: true }).safeParse({})).toThrow();
|
||||
// @ts-expect-error
|
||||
expect(() => schema.omit({ $unknown: true })).toThrow();
|
||||
expect(() => schema.omit({ $unknown: true }).safeParse({})).toThrow();
|
||||
// @ts-expect-error
|
||||
expect(() => schema.required({ $unknown: true })).toThrow();
|
||||
expect(() => schema.required({ $unknown: true }).safeParse({})).toThrow();
|
||||
// @ts-expect-error
|
||||
expect(() => schema.partial({ $unknown: true })).toThrow();
|
||||
expect(() => schema.partial({ $unknown: true }).safeParse({})).toThrow();
|
||||
});
|
||||
|
||||
@@ -73,17 +73,18 @@ test("preprocess ctx.addIssue with parse", () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test("preprocess ctx.addIssue non-fatal by default", () => {
|
||||
test("preprocess ctx.addIssue fatal by default", () => {
|
||||
const schema = z.preprocess((data, ctx) => {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
});
|
||||
|
||||
return data;
|
||||
}, z.string());
|
||||
const result = schema.safeParse(1234);
|
||||
|
||||
expect(result.error!.issues).toHaveLength(2);
|
||||
expect(result.error!.issues).toHaveLength(1);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
@@ -91,12 +92,6 @@ test("preprocess ctx.addIssue non-fatal by default", () => {
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
},
|
||||
{
|
||||
"expected": "string",
|
||||
"code": "invalid_type",
|
||||
"path": [],
|
||||
"message": "Invalid input: expected string, received number"
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
@@ -175,7 +170,7 @@ test("z.NEVER in preprocess", () => {
|
||||
expectTypeOf<foo>().toEqualTypeOf<number>();
|
||||
const result = foo.safeParse(undefined);
|
||||
|
||||
expect(result.error!.issues).toHaveLength(2);
|
||||
expect(result.error!.issues).toHaveLength(1);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
@@ -183,12 +178,6 @@ test("z.NEVER in preprocess", () => {
|
||||
"code": "custom",
|
||||
"message": "bad",
|
||||
"path": []
|
||||
},
|
||||
{
|
||||
"expected": "number",
|
||||
"code": "invalid_type",
|
||||
"path": [],
|
||||
"message": "Invalid input: expected number, received object"
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
|
||||
@@ -336,7 +336,21 @@ test("partial record", () => {
|
||||
type schema = z.infer<typeof schema>;
|
||||
expectTypeOf<schema>().toEqualTypeOf<Partial<Record<string, string>>>();
|
||||
|
||||
const Keys = z.enum(["id", "name", "email"]).or(z.never());
|
||||
const Keys = z.enum(["id", "name", "email"]); //.or(z.never());
|
||||
const Person = z.partialRecord(Keys, z.string());
|
||||
expectTypeOf<z.infer<typeof Person>>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();
|
||||
|
||||
Person.parse({
|
||||
id: "123",
|
||||
// name: "John",
|
||||
// email: "[email protected]",
|
||||
});
|
||||
|
||||
Person.parse({
|
||||
// id: "123",
|
||||
// name: "John",
|
||||
email: "[email protected]",
|
||||
});
|
||||
|
||||
expect(Person.def.keyType._zod.def.type).toEqual("enum");
|
||||
});
|
||||
|
||||
@@ -260,6 +260,73 @@ test("mutual recursion with meta", () => {
|
||||
expectTypeOf<B>().toEqualTypeOf<_B>();
|
||||
});
|
||||
|
||||
test("object utilities with recursive types", () => {
|
||||
const NodeBase = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
get children() {
|
||||
return z.array(Node).optional();
|
||||
},
|
||||
});
|
||||
|
||||
// Test extend
|
||||
const NodeOne = NodeBase.extend({
|
||||
name: z.literal("nodeOne"),
|
||||
get children() {
|
||||
return z.array(Node);
|
||||
},
|
||||
});
|
||||
|
||||
const NodeTwo = NodeBase.extend({
|
||||
name: z.literal("nodeTwo"),
|
||||
get children() {
|
||||
return z.array(Node);
|
||||
},
|
||||
});
|
||||
|
||||
// Test pick
|
||||
const PickedNode = NodeBase.pick({ id: true, name: true });
|
||||
|
||||
// Test omit
|
||||
const OmittedNode = NodeBase.omit({ children: true });
|
||||
|
||||
// Test merge
|
||||
const ExtraProps = {
|
||||
metadata: z.string(),
|
||||
get parent() {
|
||||
return Node.optional();
|
||||
},
|
||||
};
|
||||
const MergedNode = NodeBase.extend(ExtraProps);
|
||||
|
||||
// Test partial
|
||||
const PartialNode = NodeBase.partial();
|
||||
const PartialMaskedNode = NodeBase.partial({ name: true });
|
||||
|
||||
// Test required (assuming NodeBase has optional fields)
|
||||
const OptionalNodeBase = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
get children() {
|
||||
return z.array(Node).optional();
|
||||
},
|
||||
});
|
||||
const RequiredNode = OptionalNodeBase.required();
|
||||
const RequiredMaskedNode = OptionalNodeBase.required({ id: true });
|
||||
|
||||
const Node = z.union([
|
||||
NodeOne,
|
||||
NodeTwo,
|
||||
PickedNode,
|
||||
OmittedNode,
|
||||
MergedNode,
|
||||
PartialNode,
|
||||
PartialMaskedNode,
|
||||
RequiredNode,
|
||||
RequiredMaskedNode,
|
||||
]);
|
||||
});
|
||||
|
||||
test("recursion compatibility", () => {
|
||||
// array
|
||||
const A = z.object({
|
||||
|
||||
@@ -318,6 +318,83 @@ test("url validations", () => {
|
||||
expect(() => url.parse("https://")).toThrow();
|
||||
});
|
||||
|
||||
test("url preserves original input", () => {
|
||||
const url = z.string().url();
|
||||
|
||||
// Test the specific case from the user report
|
||||
const input = "https://example.com?key=NUXOmHqWNVTapJkJJHw8BfD155AuqhH_qju_5fNmQ4ZHV7u8";
|
||||
const output = url.parse(input);
|
||||
expect(output).toBe(input); // Should preserve the original input exactly
|
||||
|
||||
// Test other cases where URL constructor would normalize
|
||||
expect(url.parse("https://example.com?foo=bar")).toBe("https://example.com?foo=bar");
|
||||
expect(url.parse("http://example.com?test=123")).toBe("http://example.com?test=123");
|
||||
expect(url.parse("https://sub.example.com?param=value&other=data")).toBe(
|
||||
"https://sub.example.com?param=value&other=data"
|
||||
);
|
||||
|
||||
// Test cases with trailing slashes are preserved
|
||||
expect(url.parse("https://example.com/")).toBe("https://example.com/");
|
||||
expect(url.parse("https://example.com/path/")).toBe("https://example.com/path/");
|
||||
|
||||
// Test cases with paths and query parameters
|
||||
expect(url.parse("https://example.com/path?query=param")).toBe("https://example.com/path?query=param");
|
||||
});
|
||||
|
||||
test("url trims whitespace", () => {
|
||||
const url = z.string().url();
|
||||
|
||||
// Test trimming whitespace from URLs
|
||||
expect(url.parse(" https://example.com ")).toBe("https://example.com");
|
||||
expect(url.parse(" https://example.com/path?query=param ")).toBe("https://example.com/path?query=param");
|
||||
expect(url.parse("\t\nhttps://example.com\t\n")).toBe("https://example.com");
|
||||
expect(url.parse(" https://example.com?key=value ")).toBe("https://example.com?key=value");
|
||||
|
||||
// Test that URLs without extra whitespace are unchanged
|
||||
expect(url.parse("https://example.com")).toBe("https://example.com");
|
||||
expect(url.parse("https://example.com/path")).toBe("https://example.com/path");
|
||||
});
|
||||
|
||||
test("url normalize flag", () => {
|
||||
const normalizeUrl = z.url({ normalize: true });
|
||||
const preserveUrl = z.url(); // normalize: false/undefined by default
|
||||
|
||||
// Test that normalize flag causes URL normalization
|
||||
expect(normalizeUrl.parse("https://example.com?key=value")).toBe("https://example.com/?key=value");
|
||||
expect(normalizeUrl.parse("http://example.com?test=123")).toBe("http://example.com/?test=123");
|
||||
|
||||
// Test with already normalized URLs
|
||||
expect(normalizeUrl.parse("https://example.com/")).toBe("https://example.com/");
|
||||
expect(normalizeUrl.parse("https://example.com/path?query=param")).toBe("https://example.com/path?query=param");
|
||||
|
||||
// Test complex URLs with normalization
|
||||
expect(normalizeUrl.parse("https://example.com/../?key=value")).toBe("https://example.com/?key=value");
|
||||
expect(normalizeUrl.parse("https://example.com/./path?key=value")).toBe("https://example.com/path?key=value");
|
||||
|
||||
// Compare with non-normalize behavior
|
||||
expect(preserveUrl.parse("https://example.com?key=value")).toBe("https://example.com?key=value");
|
||||
expect(preserveUrl.parse("http://example.com?test=123")).toBe("http://example.com?test=123");
|
||||
|
||||
// Test trimming with normalize
|
||||
expect(normalizeUrl.parse(" https://example.com?key=value ")).toBe("https://example.com/?key=value");
|
||||
expect(preserveUrl.parse(" https://example.com?key=value ")).toBe("https://example.com?key=value");
|
||||
});
|
||||
|
||||
test("url normalize with hostname and protocol constraints", () => {
|
||||
const constrainedNormalizeUrl = z.url({
|
||||
normalize: true,
|
||||
protocol: /^https$/,
|
||||
hostname: /^example\.com$/,
|
||||
});
|
||||
|
||||
// Test that normalization works with constraints
|
||||
expect(constrainedNormalizeUrl.parse("https://example.com?key=value")).toBe("https://example.com/?key=value");
|
||||
|
||||
// Test that constraints are still enforced
|
||||
expect(() => constrainedNormalizeUrl.parse("http://example.com?key=value")).toThrow();
|
||||
expect(() => constrainedNormalizeUrl.parse("https://other.com?key=value")).toThrow();
|
||||
});
|
||||
|
||||
test("httpurl", () => {
|
||||
const httpUrl = z.url({
|
||||
protocol: /^https?$/,
|
||||
|
||||
@@ -1952,6 +1952,7 @@ test("input type", () => {
|
||||
"required": [
|
||||
"a",
|
||||
"d",
|
||||
"f",
|
||||
"g",
|
||||
],
|
||||
"type": "object",
|
||||
|
||||
@@ -248,3 +248,113 @@ test("async short circuit on dirty", async () => {
|
||||
]]
|
||||
`);
|
||||
});
|
||||
|
||||
test("do not continue by default", () => {
|
||||
const A = z
|
||||
.string()
|
||||
.transform((val, ctx) => {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
});
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
});
|
||||
return val;
|
||||
})
|
||||
.pipe(z.number() as any);
|
||||
expect(A.safeParse("asdf")).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
},
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
}
|
||||
`);
|
||||
|
||||
const B = z
|
||||
.string()
|
||||
.transform((val, ctx) => {
|
||||
ctx.issues.push({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
input: val,
|
||||
});
|
||||
ctx.issues.push({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
input: val,
|
||||
});
|
||||
return val;
|
||||
})
|
||||
.pipe(z.number() as any);
|
||||
expect(B.safeParse("asdf")).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
},
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
}
|
||||
`);
|
||||
|
||||
const C = z
|
||||
.string()
|
||||
.transform((val, ctx) => {
|
||||
ctx.issues.push({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
input: val,
|
||||
continue: true,
|
||||
});
|
||||
ctx.issues.push({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
input: val,
|
||||
continue: true,
|
||||
});
|
||||
return val;
|
||||
})
|
||||
.pipe(z.number() as any);
|
||||
expect(C.safeParse("asdf")).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
},
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
},
|
||||
{
|
||||
"expected": "number",
|
||||
"code": "invalid_type",
|
||||
"path": [],
|
||||
"message": "Invalid input: expected number, received string"
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ test("return valid over invalid", () => {
|
||||
});
|
||||
|
||||
test("return errors from both union arms", () => {
|
||||
const result = z.union([z.number(), z.string().refine(() => false)]).safeParse("a");
|
||||
const result = z.union([z.number(), z.boolean()]).safeParse("a");
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues).toMatchInlineSnapshot(`
|
||||
@@ -45,8 +45,9 @@ test("return errors from both union arms", () => {
|
||||
],
|
||||
[
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "Invalid input",
|
||||
"code": "invalid_type",
|
||||
"expected": "boolean",
|
||||
"message": "Invalid input: expected boolean, received string",
|
||||
"path": [],
|
||||
},
|
||||
],
|
||||
@@ -92,3 +93,44 @@ test("union values", () => {
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("non-aborted errors", () => {
|
||||
const zItemTest = z.union([
|
||||
z.object({
|
||||
date: z.number(),
|
||||
startDate: z.optional(z.null()),
|
||||
endDate: z.optional(z.null()),
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
date: z.optional(z.null()),
|
||||
startDate: z.number(),
|
||||
endDate: z.number(),
|
||||
})
|
||||
.refine((data) => data.startDate !== data.endDate, {
|
||||
error: "startDate and endDate must be different",
|
||||
path: ["endDate"],
|
||||
}),
|
||||
]);
|
||||
|
||||
const res = zItemTest.safeParse({
|
||||
date: null,
|
||||
startDate: 1,
|
||||
endDate: 1,
|
||||
});
|
||||
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
{
|
||||
"code": "custom",
|
||||
"path": [
|
||||
"endDate"
|
||||
],
|
||||
"message": "startDate and endDate must be different"
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -1128,12 +1128,12 @@ export interface $ZodCheckMimeTypeDef extends $ZodCheckDef {
|
||||
mime: util.MimeTypes[];
|
||||
}
|
||||
|
||||
export interface $ZodCheckMimeTypeInternals<T extends File = File> extends $ZodCheckInternals<T> {
|
||||
export interface $ZodCheckMimeTypeInternals<T extends schemas.File = schemas.File> extends $ZodCheckInternals<T> {
|
||||
def: $ZodCheckMimeTypeDef;
|
||||
issc: errors.$ZodIssueInvalidValue;
|
||||
}
|
||||
|
||||
export interface $ZodCheckMimeType<T extends File = File> extends $ZodCheck<T> {
|
||||
export interface $ZodCheckMimeType<T extends schemas.File = schemas.File> extends $ZodCheck<T> {
|
||||
_zod: $ZodCheckMimeTypeInternals<T>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { $ZodCheck, $ZodStringFormats } from "./checks.js";
|
||||
import { $constructor } from "./core.js";
|
||||
import type { $ZodType } from "./schemas.js";
|
||||
import type { StandardSchemaV1 } from "./standard-schema.js";
|
||||
import * as util from "./util.js";
|
||||
|
||||
///////////////////////////
|
||||
@@ -162,7 +163,7 @@ type RawIssue<T extends $ZodIssueBase> = util.Flatten<
|
||||
readonly input?: unknown;
|
||||
/** The schema or check that originated this issue. */
|
||||
readonly inst?: $ZodType | $ZodCheck;
|
||||
/** @deprecated Internal use only. If `true`, Zod will continue executing validation despite this issue. */
|
||||
/** If `true`, Zod will continue executing validation despite this issue. */
|
||||
readonly continue?: boolean | undefined;
|
||||
} & Record<string, any>
|
||||
>;
|
||||
@@ -196,13 +197,8 @@ const initializer = (inst: $ZodError, def: $ZodIssue[]): void => {
|
||||
value: def,
|
||||
enumerable: false,
|
||||
});
|
||||
Object.defineProperty(inst, "message", {
|
||||
get() {
|
||||
return JSON.stringify(def, util.jsonStringifyReplacer, 2);
|
||||
},
|
||||
enumerable: true,
|
||||
// configurable: false,
|
||||
});
|
||||
inst.message = JSON.stringify(def, util.jsonStringifyReplacer, 2);
|
||||
|
||||
Object.defineProperty(inst, "toString", {
|
||||
value: () => inst.message,
|
||||
enumerable: false,
|
||||
@@ -391,8 +387,9 @@ export function treeifyError<T>(error: $ZodError, _mapper?: any) {
|
||||
* ✖ Invalid input: expected number
|
||||
* ```
|
||||
*/
|
||||
export function toDotPath(path: (string | number | symbol)[]): string {
|
||||
export function toDotPath(_path: readonly (string | number | symbol | StandardSchemaV1.PathSegment)[]): string {
|
||||
const segs: string[] = [];
|
||||
const path: PropertyKey[] = _path.map((seg: any) => (typeof seg === "object" ? seg.key : seg));
|
||||
for (const seg of path) {
|
||||
if (typeof seg === "number") segs.push(`[${seg}]`);
|
||||
else if (typeof seg === "symbol") segs.push(`[${JSON.stringify(String(seg))}]`);
|
||||
@@ -406,14 +403,10 @@ export function toDotPath(path: (string | number | symbol)[]): string {
|
||||
return segs.join("");
|
||||
}
|
||||
|
||||
interface BaseError {
|
||||
issues: $ZodIssueBase[];
|
||||
}
|
||||
|
||||
export function prettifyError(error: BaseError): string {
|
||||
export function prettifyError(error: StandardSchemaV1.FailureResult): string {
|
||||
const lines: string[] = [];
|
||||
// sort by path length
|
||||
const issues = [...error.issues].sort((a, b) => a.path.length - b.path.length);
|
||||
const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
|
||||
|
||||
// Process each issue
|
||||
for (const issue of issues) {
|
||||
|
||||
@@ -23,7 +23,7 @@ export type $replace<Meta, S extends $ZodType> = Meta extends $output
|
||||
? { [K in keyof Meta]: $replace<Meta[K], S> }
|
||||
: Meta;
|
||||
|
||||
type MetadataType = Record<string, unknown> | undefined;
|
||||
type MetadataType = object | undefined;
|
||||
export class $ZodRegistry<Meta extends MetadataType = MetadataType, Schema extends $ZodType = $ZodType> {
|
||||
_meta!: Meta;
|
||||
_schema!: Schema;
|
||||
@@ -68,7 +68,8 @@ export class $ZodRegistry<Meta extends MetadataType = MetadataType, Schema exten
|
||||
if (p) {
|
||||
const pm: any = { ...(this.get(p) ?? {}) };
|
||||
delete pm.id; // do not inherit id
|
||||
return { ...pm, ...this._map.get(schema) } as any;
|
||||
const f = { ...pm, ...this._map.get(schema) } as any;
|
||||
return Object.keys(f).length ? f : undefined;
|
||||
}
|
||||
return this._map.get(schema) as any;
|
||||
}
|
||||
|
||||
@@ -121,6 +121,9 @@ export interface _$ZodTypeInternals {
|
||||
*/
|
||||
values?: util.PrimitiveSet | undefined;
|
||||
|
||||
/** Default value bubbled up from */
|
||||
// default?: unknown | undefined;
|
||||
|
||||
/** @internal A set of literal discriminators used for the fast path in discriminated unions. */
|
||||
propValues?: util.PropValues | undefined;
|
||||
|
||||
@@ -414,6 +417,7 @@ export const $ZodEmail: core.$constructor<$ZodEmail> = /*@__PURE__*/ core.$const
|
||||
export interface $ZodURLDef extends $ZodStringFormatDef<"url"> {
|
||||
hostname?: RegExp | undefined;
|
||||
protocol?: RegExp | undefined;
|
||||
normalize?: boolean | undefined;
|
||||
}
|
||||
export interface $ZodURLInternals extends $ZodStringFormatInternals<"url"> {
|
||||
def: $ZodURLDef;
|
||||
@@ -427,9 +431,10 @@ export const $ZodURL: core.$constructor<$ZodURL> = /*@__PURE__*/ core.$construct
|
||||
$ZodStringFormat.init(inst, def);
|
||||
inst._zod.check = (payload) => {
|
||||
try {
|
||||
const orig = payload.value;
|
||||
const url = new URL(orig);
|
||||
const href = url.href;
|
||||
// Trim whitespace from input
|
||||
const trimmed = payload.value.trim();
|
||||
// @ts-ignore
|
||||
const url = new URL(trimmed);
|
||||
|
||||
if (def.hostname) {
|
||||
def.hostname.lastIndex = 0;
|
||||
@@ -461,11 +466,13 @@ export const $ZodURL: core.$constructor<$ZodURL> = /*@__PURE__*/ core.$construct
|
||||
}
|
||||
}
|
||||
|
||||
// payload.value = url.href;
|
||||
if (!orig.endsWith("/") && href.endsWith("/")) {
|
||||
payload.value = href.slice(0, -1);
|
||||
// Set the output value based on normalize flag
|
||||
if (def.normalize) {
|
||||
// Use normalized URL
|
||||
payload.value = url.href;
|
||||
} else {
|
||||
payload.value = href;
|
||||
// Preserve the original input (trimmed)
|
||||
payload.value = trimmed;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -719,6 +726,7 @@ export const $ZodIPv6: core.$constructor<$ZodIPv6> = /*@__PURE__*/ core.$constru
|
||||
|
||||
inst._zod.check = (payload) => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
new URL(`http://[${payload.value}]`);
|
||||
// return;
|
||||
} catch {
|
||||
@@ -782,6 +790,7 @@ export const $ZodCIDRv6: core.$constructor<$ZodCIDRv6> = /*@__PURE__*/ core.$con
|
||||
const prefixNum = Number(prefix);
|
||||
if (`${prefixNum}` !== prefix) throw new Error();
|
||||
if (prefixNum < 0 || prefixNum > 128) throw new Error();
|
||||
// @ts-ignore
|
||||
new URL(`http://[${address}]`);
|
||||
} catch {
|
||||
payload.issues.push({
|
||||
@@ -801,6 +810,7 @@ export function isValidBase64(data: string): boolean {
|
||||
if (data === "") return true;
|
||||
if (data.length % 4 !== 0) return false;
|
||||
try {
|
||||
// @ts-ignore
|
||||
atob(data);
|
||||
return true;
|
||||
} catch {
|
||||
@@ -900,6 +910,7 @@ export function isValidJWT(token: string, algorithm: util.JWTAlgorithm | null =
|
||||
if (tokensParts.length !== 3) return false;
|
||||
const [header] = tokensParts;
|
||||
if (!header) return false;
|
||||
// @ts-ignore
|
||||
const parsedHeader = JSON.parse(atob(header));
|
||||
if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT") return false;
|
||||
if (!parsedHeader.alg) return false;
|
||||
@@ -1582,6 +1593,36 @@ export type $InferObjectOutput<T extends $ZodLooseShape, Extra extends Record<st
|
||||
} & Extra
|
||||
>;
|
||||
|
||||
// experimental
|
||||
// export type $InferObjectOutput<T extends $ZodLooseShape, Extra extends Record<string, unknown>> = keyof (T &
|
||||
// Extra) extends never
|
||||
// ? Record<string, never>
|
||||
// : string extends keyof T
|
||||
// ? util.Prettify<
|
||||
// {
|
||||
// [k: string]: util.IsAny<T[string]["_zod"]["output"]> extends true ? unknown : T[string]["_zod"]["output"];
|
||||
// } & $InferObjectOutputNoIndex<util.OmitIndexSignature<T>, Extra>
|
||||
// >
|
||||
// : util.Prettify<$InferObjectOutputNoIndex<T, Extra>>;
|
||||
|
||||
// export type $InferObjectOutputNoIndex<T extends $ZodLooseShape, Extra extends Record<string, unknown>> = {
|
||||
// [k in keyof T as string extends k
|
||||
// ? never
|
||||
// : k extends string
|
||||
// ? T[k] extends OptionalOutSchema
|
||||
// ? never
|
||||
// : k
|
||||
// : never]: T[k]["_zod"]["output"];
|
||||
// } & {
|
||||
// [k in keyof T as string extends k
|
||||
// ? never
|
||||
// : k extends string
|
||||
// ? T[k] extends OptionalOutSchema
|
||||
// ? k
|
||||
// : never
|
||||
// : never]?: T[k]["_zod"]["output"];
|
||||
// } & Extra;
|
||||
|
||||
export type $InferObjectInput<T extends $ZodLooseShape, Extra extends Record<string, unknown>> = string extends keyof T
|
||||
? util.IsAny<T[keyof T]> extends true
|
||||
? Record<string, unknown>
|
||||
@@ -1596,33 +1637,16 @@ export type $InferObjectInput<T extends $ZodLooseShape, Extra extends Record<str
|
||||
} & Extra
|
||||
>;
|
||||
|
||||
function handleObjectResult(result: ParsePayload, final: ParsePayload, key: PropertyKey) {
|
||||
// if(isOptional)
|
||||
function handlePropertyResult(result: ParsePayload, final: ParsePayload, key: PropertyKey, input: any) {
|
||||
if (result.issues.length) {
|
||||
final.issues.push(...util.prefixIssues(key, result.issues));
|
||||
}
|
||||
|
||||
(final.value as any)[key] = result.value;
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
(final.value as any)[key] = result.value;
|
||||
}
|
||||
} else {
|
||||
final.issues.push(...util.prefixIssues(key, result.issues));
|
||||
if (result.value === undefined) {
|
||||
if (key in input) {
|
||||
(final.value as any)[key] = undefined;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@@ -1732,41 +1756,25 @@ export const $ZodObject: core.$constructor<$ZodObject> = /*@__PURE__*/ core.$con
|
||||
// A: preserve key order {
|
||||
doc.write(`const newResult = {}`);
|
||||
for (const key of normalized.keys) {
|
||||
if (normalized.optionalKeys.has(key)) {
|
||||
const id = ids[key];
|
||||
doc.write(`const ${id} = ${parseStr(key)};`);
|
||||
const k = util.esc(key);
|
||||
doc.write(`
|
||||
const id = ids[key];
|
||||
const k = util.esc(key);
|
||||
doc.write(`const ${id} = ${parseStr(key)};`);
|
||||
doc.write(`
|
||||
if (${id}.issues.length) {
|
||||
if (input[${k}] === undefined) {
|
||||
if (${k} in input) {
|
||||
newResult[${k}] = undefined;
|
||||
}
|
||||
} else {
|
||||
payload.issues = payload.issues.concat(
|
||||
${id}.issues.map((iss) => ({
|
||||
...iss,
|
||||
path: iss.path ? [${k}, ...iss.path] : [${k}],
|
||||
}))
|
||||
);
|
||||
payload.issues = payload.issues.concat(${id}.issues.map(iss => ({
|
||||
...iss,
|
||||
path: iss.path ? [${k}, ...iss.path] : [${k}]
|
||||
})));
|
||||
}
|
||||
|
||||
if (${id}.value === undefined) {
|
||||
if (${k} in input) {
|
||||
newResult[${k}] = undefined;
|
||||
}
|
||||
} else if (${id}.value === undefined) {
|
||||
if (${k} in input) newResult[${k}] = undefined;
|
||||
} else {
|
||||
newResult[${k}] = ${id}.value;
|
||||
}
|
||||
`);
|
||||
} else {
|
||||
const id = ids[key];
|
||||
// const id = ids[key];
|
||||
doc.write(`const ${id} = ${parseStr(key)};`);
|
||||
doc.write(`
|
||||
if (${id}.issues.length) payload.issues = payload.issues.concat(${id}.issues.map(iss => ({
|
||||
...iss,
|
||||
path: iss.path ? [${util.esc(key)}, ...iss.path] : [${util.esc(key)}]
|
||||
})));`);
|
||||
doc.write(`newResult[${util.esc(key)}] = ${id}.value`);
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
doc.write(`payload.value = newResult;`);
|
||||
@@ -1811,39 +1819,16 @@ export const $ZodObject: core.$constructor<$ZodObject> = /*@__PURE__*/ core.$con
|
||||
const shape = value.shape;
|
||||
for (const key of value.keys) {
|
||||
const el = shape[key]!;
|
||||
|
||||
// do not add omitted optional keys
|
||||
// if (!(key in input)) {
|
||||
// if (optionalKeys.has(key)) continue;
|
||||
// payload.issues.push({
|
||||
// code: "invalid_type",
|
||||
// path: [key],
|
||||
// expected: "nonoptional",
|
||||
// note: `Missing required key: "${key}"`,
|
||||
// input,
|
||||
// inst,
|
||||
// });
|
||||
// }
|
||||
|
||||
const r = el._zod.run({ value: input[key], issues: [] }, ctx);
|
||||
const isOptional = el._zod.optin === "optional" && el._zod.optout === "optional";
|
||||
|
||||
if (r instanceof Promise) {
|
||||
proms.push(
|
||||
r.then((r) =>
|
||||
isOptional ? handleOptionalObjectResult(r, payload, key, input) : handleObjectResult(r, payload, key)
|
||||
)
|
||||
);
|
||||
} else if (isOptional) {
|
||||
handleOptionalObjectResult(r, payload, key, input);
|
||||
proms.push(r.then((r) => handlePropertyResult(r, payload, key, input)));
|
||||
} else {
|
||||
handleObjectResult(r, payload, key);
|
||||
handlePropertyResult(r, payload, key, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!catchall) {
|
||||
// return payload;
|
||||
return proms.length ? Promise.all(proms).then(() => payload) : payload;
|
||||
}
|
||||
const unrecognized: string[] = [];
|
||||
@@ -1860,9 +1845,9 @@ export const $ZodObject: core.$constructor<$ZodObject> = /*@__PURE__*/ core.$con
|
||||
const r = _catchall.run({ value: input[key], issues: [] }, ctx);
|
||||
|
||||
if (r instanceof Promise) {
|
||||
proms.push(r.then((r) => handleObjectResult(r, payload, key)));
|
||||
proms.push(r.then((r) => handlePropertyResult(r, payload, key, input)));
|
||||
} else {
|
||||
handleObjectResult(r, payload, key);
|
||||
handlePropertyResult(r, payload, key, input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1925,6 +1910,12 @@ function handleUnionResults(results: ParsePayload[], final: ParsePayload, inst:
|
||||
}
|
||||
}
|
||||
|
||||
const nonaborted = results.filter((r) => !util.aborted(r));
|
||||
if (nonaborted.length > 0) {
|
||||
final.value = nonaborted[0].value;
|
||||
return nonaborted[0];
|
||||
}
|
||||
|
||||
final.issues.push({
|
||||
code: "invalid_union",
|
||||
input: final.value,
|
||||
@@ -2737,8 +2728,8 @@ export const $ZodEnum: core.$constructor<$ZodEnum> = /*@__PURE__*/ core.$constru
|
||||
$ZodType.init(inst, def);
|
||||
|
||||
const values = util.getEnumValues(def.entries);
|
||||
|
||||
inst._zod.values = new Set<util.Primitive>(values);
|
||||
const valuesSet = new Set<util.Primitive>(values);
|
||||
inst._zod.values = valuesSet;
|
||||
|
||||
inst._zod.pattern = new RegExp(
|
||||
`^(${values
|
||||
@@ -2749,7 +2740,7 @@ export const $ZodEnum: core.$constructor<$ZodEnum> = /*@__PURE__*/ core.$constru
|
||||
|
||||
inst._zod.parse = (payload, _ctx) => {
|
||||
const input = payload.value;
|
||||
if (inst._zod.values.has(input)) {
|
||||
if (valuesSet.has(input)) {
|
||||
return payload;
|
||||
}
|
||||
payload.issues.push({
|
||||
@@ -2865,10 +2856,12 @@ export const $ZodLiteral: core.$constructor<$ZodLiteral> = /*@__PURE__*/ core.$c
|
||||
//////////////////////////////////////////
|
||||
|
||||
// provide a fallback in case the File interface isn't provided in the environment
|
||||
type _File = typeof globalThis extends { File: new (...args: any[]) => any }
|
||||
? InstanceType<typeof globalThis.File>
|
||||
: {};
|
||||
interface File extends _File {}
|
||||
type _File = typeof globalThis extends { File: infer F extends new (...args: any[]) => any } ? InstanceType<F> : {};
|
||||
/** Do not reference this directly. */
|
||||
export interface File extends _File {
|
||||
type: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface $ZodFileDef extends $ZodTypeDef {
|
||||
type: "file";
|
||||
@@ -2893,6 +2886,7 @@ export const $ZodFile: core.$constructor<$ZodFile> = /*@__PURE__*/ core.$constru
|
||||
|
||||
inst._zod.parse = (payload, _ctx) => {
|
||||
const input = payload.value;
|
||||
// @ts-ignore
|
||||
if (input instanceof File) return payload;
|
||||
payload.issues.push({
|
||||
expected: "file",
|
||||
@@ -3329,7 +3323,7 @@ export interface $ZodCatch<T extends SomeType = $ZodType> extends $ZodType {
|
||||
|
||||
export const $ZodCatch: core.$constructor<$ZodCatch> = /*@__PURE__*/ core.$constructor("$ZodCatch", (inst, def) => {
|
||||
$ZodType.init(inst, def);
|
||||
inst._zod.optin = "optional";
|
||||
util.defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
|
||||
util.defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
|
||||
util.defineLazy(inst._zod, "values", () => def.innerType._zod.values);
|
||||
|
||||
@@ -3362,6 +3356,7 @@ export const $ZodCatch: core.$constructor<$ZodCatch> = /*@__PURE__*/ core.$const
|
||||
},
|
||||
input: payload.value,
|
||||
});
|
||||
|
||||
payload.issues = [];
|
||||
}
|
||||
|
||||
|
||||
@@ -777,6 +777,7 @@ export class JSONSchemaGenerator {
|
||||
} else if (this.target === "draft-7") {
|
||||
result.$schema = "http://json-schema.org/draft-07/schema#";
|
||||
} else {
|
||||
// @ts-ignore
|
||||
console.warn(`Invalid target: ${this.target}`);
|
||||
}
|
||||
|
||||
|
||||
+170
-110
@@ -283,6 +283,12 @@ export function assignProp<T extends object, K extends PropertyKey>(
|
||||
});
|
||||
}
|
||||
|
||||
export function cloneDef(schema: schemas.$ZodType): any {
|
||||
const def: any = {};
|
||||
Object.defineProperties(def, Object.getOwnPropertyDescriptors(schema._zod.def));
|
||||
return def;
|
||||
}
|
||||
|
||||
export function getElementAtPath(obj: any, path: (string | number)[] | null | undefined): any {
|
||||
if (!path) return obj;
|
||||
return path.reduce((acc, key) => acc?.[key], obj);
|
||||
@@ -314,15 +320,16 @@ export function esc(str: string): string {
|
||||
return JSON.stringify(str);
|
||||
}
|
||||
|
||||
export const captureStackTrace: (targetObject: object, constructorOpt?: Function) => void = Error.captureStackTrace
|
||||
? Error.captureStackTrace
|
||||
: (..._args) => {};
|
||||
export const captureStackTrace: (targetObject: object, constructorOpt?: Function) => void = (
|
||||
"captureStackTrace" in Error ? Error.captureStackTrace : (..._args: any[]) => {}
|
||||
) as any;
|
||||
|
||||
export function isObject(data: any): data is Record<PropertyKey, unknown> {
|
||||
return typeof data === "object" && data !== null && !Array.isArray(data);
|
||||
}
|
||||
|
||||
export const allowsEval: { value: boolean } = cached(() => {
|
||||
// @ts-ignore
|
||||
if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) {
|
||||
return false;
|
||||
}
|
||||
@@ -409,6 +416,7 @@ export const getParsedType = (data: any): ParsedTypes => {
|
||||
if (typeof Date !== "undefined" && data instanceof Date) {
|
||||
return "date";
|
||||
}
|
||||
// @ts-ignore
|
||||
if (typeof File !== "undefined" && data instanceof File) {
|
||||
return "file";
|
||||
}
|
||||
@@ -535,71 +543,103 @@ export const BIGINT_FORMAT_RANGES: Record<checks.$ZodBigIntFormats, [bigint, big
|
||||
};
|
||||
|
||||
export function pick(schema: schemas.$ZodObject, mask: Record<string, unknown>): any {
|
||||
const newShape: Writeable<schemas.$ZodShape> = {};
|
||||
const currDef = schema._zod.def; //.shape;
|
||||
const currDef = schema._zod.def;
|
||||
|
||||
for (const key in mask) {
|
||||
if (!(key in currDef.shape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!mask[key]) continue;
|
||||
const def = cloneDef(schema);
|
||||
Object.defineProperties(def, {
|
||||
shape: {
|
||||
get() {
|
||||
const newShape: Writeable<schemas.$ZodShape> = {};
|
||||
for (const key in mask) {
|
||||
if (!(key in currDef.shape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!mask[key]) continue;
|
||||
newShape[key] = currDef.shape[key]!;
|
||||
}
|
||||
|
||||
// pick key
|
||||
newShape[key] = currDef.shape[key]!;
|
||||
}
|
||||
assignProp(this, "shape", newShape); // self-caching
|
||||
return newShape;
|
||||
},
|
||||
},
|
||||
checks: {
|
||||
value: [],
|
||||
},
|
||||
});
|
||||
|
||||
return clone(schema, {
|
||||
...schema._zod.def,
|
||||
shape: newShape,
|
||||
checks: [],
|
||||
}) as any;
|
||||
return clone(schema, def) as any;
|
||||
}
|
||||
|
||||
export function omit(schema: schemas.$ZodObject, mask: object): any {
|
||||
const newShape: Writeable<schemas.$ZodShape> = { ...schema._zod.def.shape };
|
||||
const currDef = schema._zod.def; //.shape;
|
||||
for (const key in mask) {
|
||||
if (!(key in currDef.shape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!(mask as any)[key]) continue;
|
||||
const currDef = schema._zod.def;
|
||||
|
||||
delete newShape[key];
|
||||
}
|
||||
return clone(schema, {
|
||||
...schema._zod.def,
|
||||
shape: newShape,
|
||||
checks: [],
|
||||
const def = cloneDef(schema);
|
||||
Object.defineProperties(def, {
|
||||
shape: {
|
||||
get() {
|
||||
const newShape: Writeable<schemas.$ZodShape> = { ...schema._zod.def.shape };
|
||||
for (const key in mask) {
|
||||
if (!(key in currDef.shape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!(mask as any)[key]) continue;
|
||||
|
||||
delete newShape[key];
|
||||
}
|
||||
assignProp(this, "shape", newShape); // self-caching
|
||||
return newShape;
|
||||
},
|
||||
},
|
||||
checks: {
|
||||
value: [],
|
||||
},
|
||||
});
|
||||
|
||||
return clone(schema, def);
|
||||
}
|
||||
|
||||
export function extend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): any {
|
||||
if (!isPlainObject(shape)) {
|
||||
throw new Error("Invalid input to extend: expected a plain object");
|
||||
}
|
||||
const def = {
|
||||
...schema._zod.def,
|
||||
get shape() {
|
||||
const _shape = { ...schema._zod.def.shape, ...shape };
|
||||
assignProp(this, "shape", _shape); // self-caching
|
||||
return _shape;
|
||||
|
||||
const def = cloneDef(schema);
|
||||
Object.defineProperties(def, {
|
||||
shape: {
|
||||
get() {
|
||||
const _shape = { ...schema._zod.def.shape, ...shape };
|
||||
assignProp(this, "shape", _shape); // self-caching
|
||||
return _shape;
|
||||
},
|
||||
},
|
||||
checks: [], // delete existing checks
|
||||
} as any;
|
||||
checks: {
|
||||
value: [],
|
||||
},
|
||||
});
|
||||
return clone(schema, def) as any;
|
||||
}
|
||||
|
||||
export function merge(a: schemas.$ZodObject, b: schemas.$ZodObject): any {
|
||||
return clone(a, {
|
||||
...a._zod.def,
|
||||
get shape() {
|
||||
const _shape = { ...a._zod.def.shape, ...b._zod.def.shape };
|
||||
assignProp(this, "shape", _shape); // self-caching
|
||||
return _shape;
|
||||
const def = cloneDef(a);
|
||||
Object.defineProperties(def, {
|
||||
shape: {
|
||||
get() {
|
||||
const _shape = { ...a._zod.def.shape, ...b._zod.def.shape };
|
||||
assignProp(this, "shape", _shape); // self-caching
|
||||
return _shape;
|
||||
},
|
||||
},
|
||||
catchall: b._zod.def.catchall,
|
||||
checks: [], // delete existing checks
|
||||
}) as any;
|
||||
catchall: {
|
||||
get() {
|
||||
return b._zod.def.catchall;
|
||||
},
|
||||
},
|
||||
checks: {
|
||||
value: [], // delete existing checks
|
||||
},
|
||||
});
|
||||
|
||||
return clone(a, def) as any;
|
||||
}
|
||||
|
||||
export function partial(
|
||||
@@ -607,40 +647,49 @@ export function partial(
|
||||
schema: schemas.$ZodObject,
|
||||
mask: object | undefined
|
||||
): any {
|
||||
const oldShape = schema._zod.def.shape;
|
||||
const shape: Writeable<schemas.$ZodShape> = { ...oldShape };
|
||||
const def = cloneDef(schema);
|
||||
Object.defineProperties(def, {
|
||||
shape: {
|
||||
get() {
|
||||
const oldShape = schema._zod.def.shape;
|
||||
const shape: Writeable<schemas.$ZodShape> = { ...oldShape };
|
||||
|
||||
if (mask) {
|
||||
for (const key in mask) {
|
||||
if (!(key in oldShape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!(mask as any)[key]) continue;
|
||||
// if (oldShape[key]!._zod.optin === "optional") continue;
|
||||
shape[key] = Class
|
||||
? new Class({
|
||||
type: "optional",
|
||||
innerType: oldShape[key]!,
|
||||
})
|
||||
: oldShape[key]!;
|
||||
}
|
||||
} else {
|
||||
for (const key in oldShape) {
|
||||
// if (oldShape[key]!._zod.optin === "optional") continue;
|
||||
shape[key] = Class
|
||||
? new Class({
|
||||
type: "optional",
|
||||
innerType: oldShape[key]!,
|
||||
})
|
||||
: oldShape[key]!;
|
||||
}
|
||||
}
|
||||
if (mask) {
|
||||
for (const key in mask) {
|
||||
if (!(key in oldShape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!(mask as any)[key]) continue;
|
||||
// if (oldShape[key]!._zod.optin === "optional") continue;
|
||||
shape[key] = Class
|
||||
? new Class({
|
||||
type: "optional",
|
||||
innerType: oldShape[key]!,
|
||||
})
|
||||
: oldShape[key]!;
|
||||
}
|
||||
} else {
|
||||
for (const key in oldShape) {
|
||||
// if (oldShape[key]!._zod.optin === "optional") continue;
|
||||
shape[key] = Class
|
||||
? new Class({
|
||||
type: "optional",
|
||||
innerType: oldShape[key]!,
|
||||
})
|
||||
: oldShape[key]!;
|
||||
}
|
||||
}
|
||||
|
||||
return clone(schema, {
|
||||
...schema._zod.def,
|
||||
shape,
|
||||
checks: [],
|
||||
}) as any;
|
||||
assignProp(this, "shape", shape); // self-caching
|
||||
return shape;
|
||||
},
|
||||
},
|
||||
checks: {
|
||||
value: [],
|
||||
},
|
||||
});
|
||||
|
||||
return clone(schema, def) as any;
|
||||
}
|
||||
|
||||
export function required(
|
||||
@@ -648,44 +697,54 @@ export function required(
|
||||
schema: schemas.$ZodObject,
|
||||
mask: object | undefined
|
||||
): any {
|
||||
const oldShape = schema._zod.def.shape;
|
||||
const shape: Writeable<schemas.$ZodShape> = { ...oldShape };
|
||||
const def = cloneDef(schema);
|
||||
Object.defineProperties(def, {
|
||||
shape: {
|
||||
get() {
|
||||
const oldShape = schema._zod.def.shape;
|
||||
const shape: Writeable<schemas.$ZodShape> = { ...oldShape };
|
||||
|
||||
if (mask) {
|
||||
for (const key in mask) {
|
||||
if (!(key in shape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!(mask as any)[key]) continue;
|
||||
// overwrite with non-optional
|
||||
shape[key] = new Class({
|
||||
type: "nonoptional",
|
||||
innerType: oldShape[key]!,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (const key in oldShape) {
|
||||
// overwrite with non-optional
|
||||
shape[key] = new Class({
|
||||
type: "nonoptional",
|
||||
innerType: oldShape[key]!,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (mask) {
|
||||
for (const key in mask) {
|
||||
if (!(key in shape)) {
|
||||
throw new Error(`Unrecognized key: "${key}"`);
|
||||
}
|
||||
if (!(mask as any)[key]) continue;
|
||||
// overwrite with non-optional
|
||||
shape[key] = new Class({
|
||||
type: "nonoptional",
|
||||
innerType: oldShape[key]!,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (const key in oldShape) {
|
||||
// overwrite with non-optional
|
||||
shape[key] = new Class({
|
||||
type: "nonoptional",
|
||||
innerType: oldShape[key]!,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return clone(schema, {
|
||||
...schema._zod.def,
|
||||
shape,
|
||||
// optional: [],
|
||||
checks: [],
|
||||
}) as any;
|
||||
assignProp(this, "shape", shape); // self-caching
|
||||
return shape;
|
||||
},
|
||||
},
|
||||
checks: {
|
||||
value: [],
|
||||
},
|
||||
});
|
||||
|
||||
return clone(schema, def) as any;
|
||||
}
|
||||
|
||||
export type Constructor<T, Def extends any[] = any[]> = new (...args: Def) => T;
|
||||
|
||||
export function aborted(x: schemas.ParsePayload, startIndex = 0): boolean {
|
||||
for (let i = startIndex; i < x.issues.length; i++) {
|
||||
if (x.issues[i]?.continue !== true) return true;
|
||||
if (x.issues[i]?.continue !== true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -733,6 +792,7 @@ export function finalizeIssue(
|
||||
export function getSizableOrigin(input: any): "set" | "map" | "file" | "unknown" {
|
||||
if (input instanceof Set) return "set";
|
||||
if (input instanceof Map) return "map";
|
||||
// @ts-ignore
|
||||
if (input instanceof File) return "file";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const version = {
|
||||
major: 4,
|
||||
minor: 0,
|
||||
patch: 5 as number,
|
||||
patch: 6 as number,
|
||||
} as const;
|
||||
|
||||
@@ -1024,9 +1024,11 @@ export function partialRecord<Key extends core.$ZodRecordKey, Value extends Some
|
||||
valueType: Value,
|
||||
params?: string | core.$ZodRecordParams
|
||||
): ZodMiniRecord<Key & core.$partial, Value> {
|
||||
const k = core.clone(keyType);
|
||||
k._zod.values = undefined;
|
||||
return new ZodMiniRecord({
|
||||
type: "record",
|
||||
keyType: union([keyType, never()]),
|
||||
keyType: k,
|
||||
valueType: valueType as any,
|
||||
...util.normalizeParams(params),
|
||||
}) as any;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"customConditions": ["@zod/source"],
|
||||
"noEmit": true,
|
||||
"types": ["vitest", "recheck"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
"include": ["**/*.ts"],
|
||||
}
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
import { z } from "zod";
|
||||
import * as z from "zod";
|
||||
|
||||
z;
|
||||
|
||||
const arg = z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
type: z.literal("play"),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
duration: z.number().int().positive(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("pause"),
|
||||
id: z.string(),
|
||||
reason: z.string().optional(),
|
||||
}),
|
||||
]);
|
||||
|
||||
arg.def;
|
||||
console.log(z.url().parse("sup")); // ❌
|
||||
|
||||
Generated
+14
-42
@@ -17,9 +17,6 @@ importers:
|
||||
'@types/benchmark':
|
||||
specifier: ^2.1.5
|
||||
version: 2.1.5
|
||||
'@types/node':
|
||||
specifier: ^20.17.30
|
||||
version: 20.17.30
|
||||
'@types/semver':
|
||||
specifier: ^7.7.0
|
||||
version: 7.7.0
|
||||
@@ -97,7 +94,7 @@ importers:
|
||||
version: 5.5.4
|
||||
vitest:
|
||||
specifier: ^2.1.9
|
||||
version: 2.1.9(@types/node@20.17.30)([email protected])([email protected])
|
||||
version: 2.1.9(@types/node@22.13.13)([email protected])([email protected])
|
||||
zod:
|
||||
specifier: workspace:*
|
||||
version: link:packages/zod
|
||||
@@ -2590,9 +2587,6 @@ packages:
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-hHTHp+sEz6SxFsp+SA+Tqrua3AbmlAw+Y//aEwdHrdZkYVRWdvWD3y5uPZ0flYOkgskaFWqZ/YGFm3FaFQ0pRw==}
|
||||
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==}
|
||||
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==}
|
||||
|
||||
@@ -3123,15 +3117,6 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -5374,9 +5359,6 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
|
||||
|
||||
@@ -7686,10 +7668,6 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/[email protected]':
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
'@types/[email protected]':
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
@@ -7753,13 +7731,13 @@ snapshots:
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/[email protected]([email protected](@types/node@20.17.30)([email protected])([email protected]))':
|
||||
'@vitest/[email protected]([email protected](@types/node@22.13.13)([email protected])([email protected]))':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.9
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
vite: 5.4.18(@types/node@20.17.30)([email protected])([email protected])
|
||||
vite: 5.4.18(@types/node@22.13.13)([email protected])([email protected])
|
||||
|
||||
'@vitest/[email protected]':
|
||||
dependencies:
|
||||
@@ -8221,10 +8199,6 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -11091,8 +11065,6 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -11231,13 +11203,13 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.2
|
||||
|
||||
[email protected](@types/node@20.17.30)([email protected])([email protected]):
|
||||
[email protected](@types/node@22.13.13)([email protected])([email protected]):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
es-module-lexer: 1.6.0
|
||||
pathe: 1.1.2
|
||||
vite: 5.4.18(@types/node@20.17.30)([email protected])([email protected])
|
||||
vite: 5.4.18(@types/node@22.13.13)([email protected])([email protected])
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
@@ -11249,28 +11221,28 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
[email protected](@types/node@20.17.30)([email protected])([email protected]):
|
||||
[email protected](@types/node@22.13.13)([email protected])([email protected]):
|
||||
dependencies:
|
||||
esbuild: 0.21.5
|
||||
postcss: 8.5.3
|
||||
rollup: 4.39.0
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.30
|
||||
'@types/node': 22.13.13
|
||||
fsevents: 2.3.3
|
||||
lightningcss: 1.29.3
|
||||
terser: 5.39.0
|
||||
|
||||
[email protected](@types/node@20.17.30)([email protected])([email protected]):
|
||||
[email protected](@types/node@22.13.13)([email protected])([email protected]):
|
||||
dependencies:
|
||||
'@vitest/expect': 2.1.9
|
||||
'@vitest/mocker': 2.1.9([email protected](@types/node@20.17.30)([email protected])([email protected]))
|
||||
'@vitest/mocker': 2.1.9([email protected](@types/node@22.13.13)([email protected])([email protected]))
|
||||
'@vitest/pretty-format': 2.1.9
|
||||
'@vitest/runner': 2.1.9
|
||||
'@vitest/snapshot': 2.1.9
|
||||
'@vitest/spy': 2.1.9
|
||||
'@vitest/utils': 2.1.9
|
||||
chai: 5.2.0
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
expect-type: 1.2.1
|
||||
magic-string: 0.30.17
|
||||
pathe: 1.1.2
|
||||
@@ -11279,11 +11251,11 @@ snapshots:
|
||||
tinyexec: 0.3.2
|
||||
tinypool: 1.0.2
|
||||
tinyrainbow: 1.2.0
|
||||
vite: 5.4.18(@types/node@20.17.30)([email protected])([email protected])
|
||||
vite-node: 2.1.9(@types/node@20.17.30)([email protected])([email protected])
|
||||
vite: 5.4.18(@types/node@22.13.13)([email protected])([email protected])
|
||||
vite-node: 2.1.9(@types/node@22.13.13)([email protected])([email protected])
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.30
|
||||
'@types/node': 22.13.13
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
- lightningcss
|
||||
|
||||
+3
-1
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"extends": "./.configs/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"customConditions": ["@zod/source"],
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext"
|
||||
"moduleResolution": "nodenext",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.mts", "**/*.cts"],
|
||||
"exclude": ["**/*.d.ts", "**/*.d.cts", "**/*.d.mts", "packages/docs"]
|
||||
|
||||
Reference in New Issue
Block a user