I found Zod a lot easier to use once we were given the "satisfies" keyword. I still basically have to write my schema twice (I don't mind this) but I can ensure the two are tightly coupled. A change to either one will show a compile error in the right place:
interface Person {
firstName: string;
lastName: string;
}
Interesting! I'll probably start doing that. I hadn't connected the satisfies keyword to this problem, but it seems like a good solution.
My complaint about writing the schemas twice isn't the busywork (which is, like, annoying, but whatever I'll live). But the concern about small discrepancies between the two causing problems later.
interface Person { firstName: string; lastName: string; }
const PersonSchema = z.object({ firstName: z.string(), lastName: z.string(), }) satisfies z.Schema<Person>;