Capítulo 30 de 61
.brand() simulates nominal typing on top of TypeScript's structural type system, so structurally-identical types (e.g. two { name: string } shapes) become type-incompatible; .readonly() marks the inferred type as read-only and actually freezes the parsed result at runtime.
.brand<"Tag">(): attaches a phantom brand to the schema's output type by default; a value must be produced by .parse() on that exact branded schema to satisfy the branded type — plain unbranded data of the same shape is rejected by the type checker..parse()'s actual return value is unaffected..brand<"Tag", "out">() (default, brands output), "in" (brands input), "inout" (brands both)..readonly(): marks the inferred type Readonly<...> for objects/arrays/tuples/Map/Set, and actually calls Object.freeze() on the parsed result, so mutation attempts throw a TypeError at runtime — not just a type error.const Cat = z.object({ name: z.string() }).brand<"Cat">();
const Dog = z.object({ name: z.string() }).brand<"Dog">();
const pluto = Dog.parse({ name: "pluto" });
const simba: z.infer<typeof Cat> = pluto; // type error — Dog isn't assignable to Cat
const ReadonlyUser = z.object({ name: z.string() }).readonly();
const result = ReadonlyUser.parse({ name: "fido" });
result.name = "simba"; // throws TypeError at runtime — object is frozen
.brand() to validate anything at runtime: it doesn't — branding only affects the static type; two objects with the same shape are still runtime-identical regardless of brand..brand() when you want the type system to prevent mixing up semantically distinct values that happen to share a shape (e.g. UserId vs OrderId, both string)..readonly() is not just cosmetic typing — the parsed object is actually frozen, so runtime mutation attempts fail loudly.