Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/treeshake/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
out.js
out-*
meta.json
18 changes: 5 additions & 13 deletions packages/treeshake/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,14 @@
"type": "module",
"license": "MIT",
"scripts": {
"bundle": "rollup -c rollup.config.js --input",
"bundle:rollup": "rollup --input",
"bundle:esbuild": "esbuild --bundle ./in.ts --conditions=@zod/source --outfile=./out.js --bundle --format=esm"
"bundle": "esbuild --bundle --format=esm --conditions=@zod/source --outfile=./out.js",
"bundle:no-source": "esbuild --bundle --format=esm --outfile=./out.js"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"@types/node": "^22.13.13",
"zod": "workspace:*",
"rollup": "^4.37.0",
"rollup-plugin-filesize": "^10.0.0",
"rollup-plugin-bundle-size": "^1.0.3",
"esbuild": "^0.24.0",
"valibot": "^1.0.0",
"zod": "^4.1.12",
"zod3": "npm:zod@^3.24.0",
"zod4": "npm:zod@^3.25.0"
"zod4": "npm:zod@4.0.0"
}
}
27 changes: 0 additions & 27 deletions packages/treeshake/rollup.config.js

This file was deleted.

5 changes: 2 additions & 3 deletions packages/zod/src/v4/classic/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,10 @@ export const _ZodString: core.$constructor<_ZodString> = /*@__PURE__*/ core.$con

const bag = inst._zod.bag;
inst.format = bag.format ?? null;
inst.minLength = bag.minimum ?? null;
inst.maxLength = bag.maximum ?? null;
inst.minLength = (bag.minimum as number) ?? null;
inst.maxLength = (bag.maximum as number) ?? null;

// validations
// if (inst.regex) throw new Error("regex already defined");
inst.regex = (...args) => inst.check(checks.regex(...args));
inst.includes = (...args) => inst.check(checks.includes(...args));
inst.startsWith = (...args) => inst.check(checks.startsWith(...args));
Expand Down
4 changes: 4 additions & 0 deletions packages/zod/src/v4/core/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,7 @@ export const $ZodCheckStringFormat: core.$constructor<$ZodCheckStringFormat> = /
if (def.pattern) {
bag.patterns ??= new Set();
bag.patterns.add(def.pattern);
bag.pattern = def.pattern.source;
}
});

Expand Down Expand Up @@ -967,6 +968,7 @@ export const $ZodCheckIncludes: core.$constructor<$ZodCheckIncludes> = /*@__PURE
const bag = inst._zod.bag as schemas.$ZodStringInternals<unknown>["bag"];
bag.patterns ??= new Set();
bag.patterns.add(pattern);
bag.pattern = pattern.source;
});

inst._zod.check = (payload) => {
Expand Down Expand Up @@ -1011,6 +1013,7 @@ export const $ZodCheckStartsWith: core.$constructor<$ZodCheckStartsWith> = /*@__
const bag = inst._zod.bag as schemas.$ZodStringInternals<unknown>["bag"];
bag.patterns ??= new Set();
bag.patterns.add(pattern);
bag.pattern = pattern.source;
});

inst._zod.check = (payload) => {
Expand Down Expand Up @@ -1055,6 +1058,7 @@ export const $ZodCheckEndsWith: core.$constructor<$ZodCheckEndsWith> = /*@__PURE
const bag = inst._zod.bag as schemas.$ZodStringInternals<unknown>["bag"];
bag.patterns ??= new Set();
bag.patterns.add(pattern);
bag.pattern = pattern.source;
});

inst._zod.check = (payload) => {
Expand Down
266 changes: 266 additions & 0 deletions packages/zod/src/v4/core/json-schema-equality.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { describe, expect, it } from "vitest";
import * as z from "../classic/index.js";

/**
* Tests to ensure _zod.getJSONSchema() produces the same output as z.toJSONSchema()
* for simple cases (no $defs, no custom registry, input mode).
*/

import type { JSONSchemaContext } from "./json-schema-lite.js";

const testContext: JSONSchemaContext = {
io: "input",
target: "draft-2020-12",
path: [],
schemaPath: [],
unrepresentable: "throw",
processor: (_schema, context, result) => {
// Check for unrepresentable types (indicated by _error property)
if ("_error" in result) {
if (context.unrepresentable === "throw") {
throw new Error((result as any)._error as string);
}
return {};
}
return result;
},
};

function assertEqualSchemas(_name: string, schema: any) {
const lite = schema._zod.getJSONSchema(testContext);
const full = z.toJSONSchema(schema, { io: "input" });

// Remove $schema for comparison since lite version doesn't include it
const fullWithoutSchema = { ...full };
delete fullWithoutSchema.$schema;

expect(lite).toEqual(fullWithoutSchema);
}

describe("JSON Schema Equality Tests", () => {
describe("Primitives", () => {
it("string", () => {
assertEqualSchemas("string", z.string());
});

it("string with constraints", () => {
assertEqualSchemas("string with constraints", z.string().min(5).max(10));
});

it("email", () => {
assertEqualSchemas("email", z.email());
});

it("url", () => {
assertEqualSchemas("url", z.url());
});

it("uuid", () => {
assertEqualSchemas("uuid", z.uuid());
});

it("number", () => {
assertEqualSchemas("number", z.number());
});

it("number with constraints", () => {
assertEqualSchemas("number with constraints", z.number().min(0).max(100));
});

it("number with exclusive constraints", () => {
assertEqualSchemas("number with exclusive constraints", z.number().gt(0).lt(100));
});

it("integer", () => {
assertEqualSchemas("integer", z.int());
});

it("boolean", () => {
assertEqualSchemas("boolean", z.boolean());
});

it("null", () => {
assertEqualSchemas("null", z.null());
});
});

describe("Arrays", () => {
it("array of strings", () => {
assertEqualSchemas("array of strings", z.array(z.string()));
});

it("array with constraints", () => {
assertEqualSchemas("array with constraints", z.array(z.string()).min(1).max(5));
});

it("nested array", () => {
assertEqualSchemas("nested array", z.array(z.array(z.number())));
});
});

describe("Objects", () => {
it("simple object", () => {
assertEqualSchemas(
"simple object",
z.object({
name: z.string(),
age: z.number(),
})
);
});

it("object with optional field", () => {
assertEqualSchemas(
"object with optional field",
z.object({
name: z.string(),
age: z.number().optional(),
})
);
});

it("nested object", () => {
assertEqualSchemas(
"nested object",
z.object({
user: z.object({
name: z.string(),
email: z.email(),
}),
metadata: z.object({
created: z.string(),
updated: z.string().optional(),
}),
})
);
});

it("empty object", () => {
assertEqualSchemas("empty object", z.object({}));
});
});

describe("Unions", () => {
it("string or number", () => {
assertEqualSchemas("string or number", z.union([z.string(), z.number()]));
});

it("complex union", () => {
assertEqualSchemas("complex union", z.union([z.string(), z.number(), z.object({ type: z.literal("obj") })]));
});

it("nullable", () => {
assertEqualSchemas("nullable", z.string().nullable());
});
});

describe("Enums and Literals", () => {
it("string enum", () => {
assertEqualSchemas("string enum", z.enum(["red", "green", "blue"]));
});

it("mixed enum", () => {
assertEqualSchemas("mixed enum", z.enum(["active", "inactive"]));
});

it("string literal", () => {
assertEqualSchemas("string literal", z.literal("hello"));
});

it("number literal", () => {
assertEqualSchemas("number literal", z.literal(42));
});

it("boolean literal", () => {
assertEqualSchemas("boolean literal", z.literal(true));
});

it("null literal", () => {
assertEqualSchemas("null literal", z.literal(null));
});
});

describe("Tuples", () => {
it("simple tuple", () => {
assertEqualSchemas("simple tuple", z.tuple([z.string(), z.number()]));
});

it("tuple with optional elements", () => {
assertEqualSchemas(
"tuple with optional elements",
z.tuple([z.string(), z.number().optional(), z.boolean().optional()])
);
});

it("empty tuple", () => {
assertEqualSchemas("empty tuple", z.tuple([]));
});
});

describe("Records", () => {
it("record with string keys and number values", () => {
assertEqualSchemas("record", z.record(z.string(), z.number()));
});

it("record with complex values", () => {
assertEqualSchemas(
"record with complex values",
z.record(
z.string(),
z.object({
id: z.number(),
name: z.string(),
})
)
);
});
});

describe("Wrappers", () => {
it("optional", () => {
assertEqualSchemas("optional", z.string().optional());
});

it("default", () => {
assertEqualSchemas("default", z.string().default("hello"));
});

it("nullable", () => {
assertEqualSchemas("nullable", z.string().nullable());
});

it("chained wrappers", () => {
assertEqualSchemas("chained wrappers", z.string().optional().nullable());
});
});

describe("Complex Combinations", () => {
it("complex nested schema", () => {
assertEqualSchemas(
"complex nested schema",
z.object({
users: z.array(
z.object({
id: z.number(),
name: z.string().min(1),
email: z.email(),
role: z.enum(["admin", "user", "guest"]),
metadata: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
})
),
pagination: z.object({
page: z.number().min(1),
limit: z.number().min(1).max(100),
total: z.number(),
}),
filters: z
.object({
search: z.string().optional(),
status: z.enum(["active", "inactive"]).optional(),
})
.optional(),
})
);
});
});
});
Loading