zod
zod copied to clipboard
3.20 introduced breaking change for `ZodDiscriminatedUnionDef`
I just wanted to note that 3.20, while claiming to have no breaking changes, did change the number of type arguments for ZodDiscriminatedUnionDef from 3 to 2. This led to breakages such as https://github.com/StefanTerdell/zod-to-json-schema/issues/31, which may be tricky to navigate in a cross-version-compatible manner.
I export literally everything from types.ts so people have an easier time building things on top of Zod. The downside is that there is virtually no change I can make that won't be "breaking" if someone relies on a particular type alias. For that reason, I consider the public API to be the z.
But historically I haven't considered the structure of Defs or the generic type signatures of each subclass part of the public API, hence "no breaking changes". This definitely makes it tricky to I'll take more care to minimize changes to the internals to account for the growing ecosystem of tools that rely on Zod.
That said, 3.20 is a very major release for Zod. I think it's likely that Zod will never move past v3, since it's been a year and a half, and I haven't encountered any reason to do a big rewrite or (public) API breakage. Despite that fact that its "minor", 3.20 is a really big release, I think the best move for ecosystem authors is to treat this as a major release, update to Zod 3.20, and specify zod@^3.20.0 in their peerDeps.
@colinhacks Why not release it as a 4, if you yourself consider it a big release? Not like you are running out of numbers any time soon.
It's not worth the disruption to users in my opinion. 98% of users don't know enough about Zod internals to understand the "breakages" even if I explained them, even with a big release like this one. Upgrading to a new major is stressful.
I still count public types as part of the contract and standard semver expectations were arguably broken here. But for us, it's mainly that one library that bit us and it seems to be maintained, so I'm sure we'll be in the clear soon when they release a new version. Not a big deal. Just know that this approach implies a form of pain and risk as well. Both in terms of us getting compilation errors just as an effect of a renovate-bot minor bump, and in terms of that utility library likely going to have a hard time supporting both pre-3.20 and post-3.20 at the same time.
In case anyone is wondering how to support < 3.20 and >= 3.20 with no type errors (IE if your library parses discriminated union defs using zod as a peer dependency), you can check if the def has optionsMap as a property.
If it does, it's 3.20 use that as the options map, if it doesn't options is the options map and you should use that. Here's a way to do it:
type OptionsMap = Map<Primitive, AnyZodObject>;
type ZodDiscriminatedUnionThreePointTwenty = {
optionsMap: OptionsMap;
discriminator: string;
};
type ZodDiscriminatedUnionPreThreePointTwenty = {
options: OptionsMap;
discriminator: string;
};
type ZodDiscriminatedUnionDefUnversioned =
| ZodDiscriminatedUnionPreThreePointTwenty
| ZodDiscriminatedUnionThreePointTwenty;
function isZodThreePointTwenty(
def: ZodDiscriminatedUnionDefUnversioned
): def is ZodDiscriminatedUnionThreePointTwenty {
return "optionsMap" in def;
}
function makeDefConsistent(
def: ZodDiscriminatedUnionDefUnversioned
): {
typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion;
discriminator: string;
options: Map<Primitive, AnyZodObject>;
} {
const optionsMap = isZodThreePointTwenty(def) ? def.optionsMap : def.options;
return {
typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,
discriminator: def.discriminator,
options: optionsMap
};
}
console.log("def:");
console.log(makeDefConsistent(discUnion._def));
Should work, typescript says it works ¯_(ツ)_/¯. I still had to use as any to pass in the function parameter elsewhere in my code though just a headsup. ALSO idk if this explodes in some situations or not, just a starting point
Is this conversation still ongoing? If not, I'd like to close this issue.