import { expectTypeOf, test } from "vitest"; import * as z from "zod/v4"; import * as core from "zod/v4/core"; test("assignability", () => { // $ZodString z.string() satisfies z.core.$ZodString; z.string() satisfies z.ZodString; // $ZodNumber z.number() satisfies z.core.$ZodNumber; z.number() satisfies z.ZodNumber; // $ZodBigInt z.bigint() satisfies z.core.$ZodBigInt; z.bigint() satisfies z.ZodBigInt; // $ZodBoolean z.boolean() satisfies z.core.$ZodBoolean; z.boolean() satisfies z.ZodBoolean; // $ZodDate z.date() satisfies z.core.$ZodDate; z.date() satisfies z.ZodDate; // $ZodSymbol z.symbol() satisfies z.core.$ZodSymbol; z.symbol() satisfies z.ZodSymbol; // $ZodUndefined z.undefined() satisfies z.core.$ZodUndefined; z.undefined() satisfies z.ZodUndefined; // $ZodNullable z.string().nullable() satisfies z.core.$ZodNullable; z.string().nullable() satisfies z.ZodNullable; // $ZodNull z.null() satisfies z.core.$ZodNull; z.null() satisfies z.ZodNull; // $ZodAny z.any() satisfies z.core.$ZodAny; z.any() satisfies z.ZodAny; // $ZodUnknown z.unknown() satisfies z.core.$ZodUnknown; z.unknown() satisfies z.ZodUnknown; // $ZodNever z.never() satisfies z.core.$ZodNever; z.never() satisfies z.ZodNever; // $ZodVoid z.void() satisfies z.core.$ZodVoid; z.void() satisfies z.ZodVoid; // $ZodArray z.array(z.string()) satisfies z.core.$ZodArray; z.array(z.string()) satisfies z.ZodArray; z.array(z.string()) satisfies z.ZodType>; // $ZodObject z.object({ key: z.string() }) satisfies z.core.$ZodObject; z.object({ key: z.string() }) satisfies z.ZodObject<{ key: z.ZodType }>; z.object({ key: z.string() }) satisfies z.ZodType<{ key: string }>; // $ZodUnion z.union([z.string(), z.number()]) satisfies z.core.$ZodUnion; z.union([z.string(), z.number()]) satisfies z.ZodUnion; z.union([z.string(), z.number()]) satisfies z.ZodType; // $ZodIntersection z.intersection(z.string(), z.number()) satisfies z.core.$ZodIntersection; z.intersection(z.string(), z.number()) satisfies z.ZodIntersection; // $ZodTuple z.tuple([z.string(), z.number()]) satisfies z.core.$ZodTuple; z.tuple([z.string(), z.number()]) satisfies z.ZodTuple; // $ZodRecord z.record(z.string(), z.number()) satisfies z.core.$ZodRecord; z.record(z.string(), z.number()) satisfies z.ZodRecord; // $ZodMap z.map(z.string(), z.number()) satisfies z.core.$ZodMap; z.map(z.string(), z.number()) satisfies z.ZodMap; // $ZodSet z.set(z.string()) satisfies z.core.$ZodSet; z.set(z.string()) satisfies z.ZodSet; // $ZodLiteral z.literal("example") satisfies z.core.$ZodLiteral; z.literal("example") satisfies z.ZodLiteral; // $ZodEnum z.enum(["a", "b", "c"]) satisfies z.core.$ZodEnum; z.enum(["a", "b", "c"]) satisfies z.ZodEnum; // $ZodPromise z.promise(z.string()) satisfies z.core.$ZodPromise; z.promise(z.string()) satisfies z.ZodPromise; // $ZodLazy const lazySchema = z.lazy(() => z.string()); lazySchema satisfies z.core.$ZodLazy; lazySchema satisfies z.ZodLazy; // $ZodOptional z.string().optional() satisfies z.core.$ZodOptional; z.string().optional() satisfies z.ZodOptional; // $ZodDefault z.string().default("default") satisfies z.core.$ZodDefault; z.string().default("default") satisfies z.ZodDefault; // $ZodTemplateLiteral z.templateLiteral([z.literal("a"), z.literal("b")]) satisfies z.core.$ZodTemplateLiteral; z.templateLiteral([z.literal("a"), z.literal("b")]) satisfies z.ZodTemplateLiteral; // $ZodCustom z.custom((val) => typeof val === "string") satisfies z.core.$ZodCustom; z.custom((val) => typeof val === "string") satisfies z.ZodCustom; // $ZodTransform z.transform((val) => val as string) satisfies z.core.$ZodTransform; z.transform((val) => val as string) satisfies z.ZodTransform; // $ZodNonOptional z.string().optional().nonoptional() satisfies z.core.$ZodNonOptional; z.string().optional().nonoptional() satisfies z.ZodNonOptional; // $ZodReadonly z.object({ key: z.string() }).readonly() satisfies z.core.$ZodReadonly; z.object({ key: z.string() }).readonly() satisfies z.ZodReadonly; // $ZodNaN z.nan() satisfies z.core.$ZodNaN; z.nan() satisfies z.ZodNaN; // $ZodPipe z.unknown().pipe(z.number()) satisfies z.core.$ZodPipe; z.unknown().pipe(z.number()) satisfies z.ZodPipe; // $ZodPreprocess z.preprocess((v) => v, z.number()) satisfies z.core.$ZodPreprocess; z.preprocess((v) => v, z.number()) satisfies z.ZodPreprocess; z.preprocess((v) => v, z.number()) satisfies z.core.$ZodPipe; z.preprocess((v) => v, z.number()) satisfies z.ZodPipe; // $ZodSuccess z.success(z.string()) satisfies z.core.$ZodSuccess; z.success(z.string()) satisfies z.ZodSuccess; // $ZodCatch z.string().catch("fallback") satisfies z.core.$ZodCatch; z.string().catch("fallback") satisfies z.ZodCatch; // $ZodFile z.file() satisfies z.core.$ZodFile; z.file() satisfies z.ZodFile; }); test("toZod", () => { type Company = { id: string; name: string; webAddress: string | null; }; const CompanySchema = z.object({ id: z.string(), name: z.string(), webAddress: z.string().url().nullable(), }); const Company = z.toZod()(CompanySchema); Company.shape.id.min(1); expectTypeOf>().toEqualTypeOf(); const CoreCompany = core.toZod()(CompanySchema); expectTypeOf>().toEqualTypeOf(); const UtilCompany = z.core.util.toZod()(CompanySchema); expectTypeOf>().toEqualTypeOf(); type Complex = { kind: "company"; nested: { founded: Date; tags: string[]; }; scores: Record<"a" | "b", number>; tuple: [string, number]; status: "active" | "inactive"; }; const Complex = z.toZod()( z.object({ kind: z.literal("company"), nested: z.object({ founded: z.date(), tags: z.array(z.string()), }), scores: z.record(z.enum(["a", "b"]), z.number()), tuple: z.tuple([z.string(), z.number()]), status: z.union([z.literal("active"), z.literal("inactive")]), }) ); expectTypeOf>().toEqualTypeOf(); type Items = { items: { value: string }[]; }; const Items = z.toZod()( z.object({ items: z.array(z.object({ value: z.string() })), }) ); expectTypeOf>().toEqualTypeOf(); type ReadonlyItem = Readonly<{ id: string; }>; const ReadonlyItem = z.toZod()(z.object({ id: z.string() }).readonly()); expectTypeOf>().toEqualTypeOf(); const Transformed = z.toZod<{ count: number }>()( z.object({ count: z.string().transform((value) => value.length), }) ); expectTypeOf>().toEqualTypeOf<{ count: string }>(); expectTypeOf>().toEqualTypeOf<{ count: number }>(); // An enum target matches z.enum(...) even though the enum reference is not identical to the union of its members (#6524) enum SkillLevel { Noob = "noob", Pro = "pro", } enum Rank { First = 1, Second = 2, } z.toZod()(z.enum(SkillLevel)); const Enums = z.toZod<{ skill: SkillLevel; members: SkillLevel.Noob | SkillLevel.Pro; rank: Rank; maybe: SkillLevel | null; list: SkillLevel[]; nested: { skill: SkillLevel }; }>()( z.object({ skill: z.enum(SkillLevel), members: z.enum(SkillLevel), rank: z.enum(Rank), maybe: z.enum(SkillLevel).nullable(), list: z.array(z.enum(SkillLevel)), nested: z.object({ skill: z.enum(SkillLevel) }), }) ); expectTypeOf["skill"]>().toEqualTypeOf(); // @ts-expect-error a member subset is not the enum z.toZod<{ skill: SkillLevel }>()(z.object({ skill: z.literal(SkillLevel.Noob) })); // @ts-expect-error plain literals do not match a nominal enum target z.toZod<{ skill: SkillLevel }>()(z.object({ skill: z.enum(["noob", "pro"]) })); // @ts-expect-error an enum schema does not match a plain literal-union target z.toZod<{ skill: "noob" | "pro" }>()(z.object({ skill: z.enum(SkillLevel) })); // @ts-expect-error plain numeric literals do not match a numeric enum target either z.toZod<{ rank: Rank }>()(z.object({ rank: z.union([z.literal(1), z.literal(2)]) })); z.toZod()( // @ts-expect-error missing optional keys still fail exact output matching z.object({ id: z.string(), name: z.string(), }) ); z.toZod()( // @ts-expect-error extra keys fail exact output matching z.object({ id: z.string(), name: z.string(), webAddress: z.string().url().nullable(), extra: z.string(), }) ); z.toZod()( // @ts-expect-error wrong property output fails matching z.object({ id: z.number(), name: z.string(), webAddress: z.string().url().nullable(), }) ); z.toZod()( // @ts-expect-error readonly output is required z.object({ id: z.string(), }) ); // @ts-expect-error any is only an exact match for any z.toZod()(z.any()); // @ts-expect-error toZod checks output, not input z.toZod()(z.string().transform((value) => value.length)); // Exact equality is deliberately the only mode. A bidirectional-assignability mode would accept both of the two cases above — `any` is assignable in both directions by construction, and `readonly` is not part of assignability — so it would silently drop the guarantees the rest of this test pins. // An intersection and the flat object with the same keys are interchangeable as targets, since the normalizer flattens both. type Intersected = { a: number } & { b: string }; const viaAnd = z.object({ a: z.number() }).and(z.object({ b: z.string() })); const viaExtend = z.object({ a: z.number() }).safeExtend({ b: z.string() }); z.toZod()(viaAnd); z.toZod()(viaExtend); z.toZod<{ a: number; b: string }>()(viaAnd); z.toZod<{ a: number; b: string }>()(viaExtend); // @ts-expect-error a missing key is still a missing key z.toZod()(z.object({ a: z.number() })); // `keyof` a function is `never`, so the normalizer must not walk into one — mapping it would erase the signature and make every callable compare equal z.toZod<() => string>()(z.custom<() => string>()); // @ts-expect-error an incompatible callable output is still rejected z.toZod<() => string>()(z.custom<() => number>()); // @ts-expect-error and so is an incompatible function-valued property z.toZod<{ cb: (x: number) => void }>()(z.object({ cb: z.custom<(x: string) => void>() })); // @ts-expect-error a method-bearing generic keeps its type arguments z.toZod<{ m: Map }>()(z.object({ m: z.custom>() })); }); test("checks", () => { const _a: z.core.$ZodCheck = {} as any as z.core.$ZodChecks; const _b: z.core.$ZodCheck = {} as any as z.core.$ZodStringFormatChecks; const _c: z.core.$ZodType = {} as any as z.core.$ZodTypes; const _d: z.core.$ZodType = {} as any as z.core.$ZodStringFormatTypes; z.currencyCode() satisfies z.core.$ZodStringFormatTypes; }); test("assignability to $ZodType", () => { z.string() satisfies z.ZodType; z.number() satisfies z.ZodType; z.boolean() satisfies z.ZodType; z.object({ key: z.string() }) satisfies z.ZodType; z.object({ key: z.string() }) satisfies z.ZodType<{ key: string }>; z.array(z.string()) satisfies z.ZodType; z.union([z.string(), z.number()]) satisfies z.ZodType; z.intersection(z.string(), z.number()) satisfies z.ZodType; z.tuple([z.string(), z.number()]) satisfies z.ZodType; z.record(z.string(), z.number()) satisfies z.ZodType; z.map(z.string(), z.number()) satisfies z.ZodType; z.set(z.string()) satisfies z.ZodType; z.literal("example") satisfies z.ZodType; expectTypeOf().toEqualTypeOf(); }); test("assignability with narrowing", () => { type _RefinedSchema | z.ZodUnion> = T extends z.ZodUnion ? RefinedUnionSchema // <-- Type instantiation is excessively deep and possibly infinite. : T extends z.ZodType ? RefinedTypeSchema> // <-- Type instantiation is excessively deep and possibly infinite. : never; type RefinedTypeSchema = T; type RefinedUnionSchema = T; }); test("generic assignability in objects", () => { interface SortItem { key: T; order: string; } const createSortItemSchema = >(sortKeySchema: T) => z.object({ key: sortKeySchema, order: z.string(), }); >(sortKeySchema: T, defaultSortBy: SortItem>[] = []) => createSortItemSchema(sortKeySchema).array().default(defaultSortBy); });