Hacker Newsnew | past | comments | ask | show | jobs | submit | colinmcd's commentslogin

As you allude to: your aliased "zod-next" dependency wouldn't be able to satisfy the requirements of any packages with a peer dep on Zod. But this approach has a more fundamental flaw. My goal is to let ecosystem libraries support Zod 3 and Zod 4 simultaneously. There's no reliable way to do that if they aren't in the same package.[0]

[0] https://github.com/colinhacks/zod/issues/4371


It won't typecheck, which is good in this case, because as you say that's a very bad idea :)


This is not done for Zod's benefit. It's done for the benefit of libraries that depend on Zod, and the users of those libraries. If a library wants to add "incremental" support for Zod4 (that is, without dropping support for Zod 3 and publishing a new major), they need access to types (and possibly runtime code) from both libraries to do so in a sound way. I detail a number of other approaches for achieving this here[0] and why they ultimately fall short. Ultimately npm wasn't designed for this particular set of circumstances.

[0] https://github.com/colinhacks/zod/issues/4371


Author here, AMA!

Regarding the versioning: I wrote a fairly detailed writeup here[0] for those who are interested in the reasons for this approach.

Ultimately npm is not designed to handle the situation Zod finds itself in. Zod is subject to a bunch of constraints that virtually no other libraries are subject to. Namely, the are dozens or hundreds of libraries that directly import interfaces/classes from Zod and use them in their own public-facing API.

Since these libraries are directly coupled to Zod, they would need to publish a new major version whenever Zod does. That's ultimately reasonable in isolation, but in Zod's case it would trigger a "version avalanche" would just be painful for everyone involved. Selfishly, I suspect it would result in a huge swath of the ecosystem pinning on v3 forever.

The approach I ended up using is analogous to what Golang does. In essence a given package never publishes new breaking versions: they just add a new subpath when a new breaking release is made. In the TypeScript ecosystem, this means libraries can configure a single peer dependency on zod@^3.25.0 and support both versions simultaneously by importing what they need from "zod/v3" and "zod/v4". It provides a nice opt-in incremental upgrade path for end-users of Zod too.

[0] https://github.com/colinhacks/zod/issues/4371


Many thanks for your work! Definitely looking forward to the upgrade - especially the performance-boosts with regards to tsc will be very welcome in our relatively large code-base, and the changes to discriminated unions will probably help us big time in a very specific scenario where so far they fell short.

That being said, I'm fully understanding of the reasons for the somewhat odd versioning given your special situation, but still, I'd wish there would be a 4.0.0-package for folks like us who simply don't need to worry or bother about zod-version-clashes in transitive dependencies bacause those don't exist (or at least I think so; npm ls zod only returns our own dependency of zod). If I understood correctly, we'll need to adapt the import to "zod/v4", which will be an incredibly noisy change and will probably cause quite a few headaches when IDEs auto-import from 'zod' and such, which we then need to catch with linting-rules.

But that's probably a small gripe for a what sounds overall like a very promising upgrade - many thanks for your work once again!


+1. We use Zod only for internal validation and would love a standalone release for v4 (or another package like zod4 or @zod/zod). I'm sure many greenfield projects would prefer that, too.


> The approach I ended up using is analogous to what Golang does. In essence a given package never publishes new breaking versions: they just add a new subpath when a new breaking release is made. In the TypeScript ecosystem, this means libraries can configure a single peer dependency on zod@^3.25.0 and support both versions simultaneously by importing what they need from "zod/v3" and "zod/v4". It provides a nice opt-in incremental upgrade path for end-users of Zod too.

This is extremely sensible. And it means you can provide security updates for older versions, just all in the same codebase's releases.


Sorry if it's mentioned in the article but I'm on mobile.

Is fixing .optional() in TS[0] part of the 9/10 top-issues fixed? This has been my biggest pain point with Zod... but still Zod is so good I still choose to just deal with it :) Thanks for an amazing part of the ecosystem.

[0] https://github.com/colinhacks/zod/issues/635


Yes! Zod now differentiates between `z.string().optional()` and `z.union([z.string(), z.undefined()])` (as in TypeScript itself). Details: https://x.com/colinhacks/status/1919291504587137496


First thank you for the hard work, many of my local hacks will go away with the new features!

As a convenience and mostly avoid typos in form names I use my own version of https://github.com/raflymln/zod-key-parser. I've been surprised something like this hasn't been implemented directly in the library.

Curious if you think this is out of scope for Zod or just something you haven't gotten around to implement?

(Here are discussions around it: https://github.com/colinhacks/zod/discussions/2134)


Some kind of affordance for FormData/URLSearchParams-style structures is definitely in scope. It was a late cut. Ultimately HTML inputs/forms are an implicit type system unto itself—certainly HTML has a very different notion of "optional" than TypeScript. So my approach to this would likely involve another sub-library ("zod/v4-form").

I like your library! Put in a PR to add it to the ecosystem page :)


Great to hear this is something you are considering!

To be clear: this isn't my library. This is just something I found while trying to solve the FormData issue. Props go to https://github.com/raflymln who created it.


Am I to understand the earliest version of Zod 4 is 3.25.0?

Does this not sound insane?

---

I've been using the alpha versions of Zod for months, I just want to edit package.json and upgrade. But now I need to shotgun across git history instead.

Colin, I appreciate your project immensely. As a point of feedback, you made this ^ much harder than you had to. (Perhaps publish a 4.x along with the 3.x stuff?)


I understand this as a knee-jerk reaction. I didn't do this lightly.

> Perhaps publish a 4.x along with the 3.x stuff

You have some misconceptions about how npm works. Unfortunately it's less reasonable than you think. There's a single `latest` tag, and there's only one "latest" version at a time. It's expected that successive versions here will follow semver. Once I publish zod@4 I can no longer publish additional zod@3.x.x versions. The workaround here is to publish v3 versions to a separate dist tag (zod@three) but anyone consuming that dist-tag (e.g. "zod": "three" in their package.json) loses the ability to specify a semver range.

I recommend reading the writeup[0]. I don't think you're appreciating the magnitude of the disruption a simple major version bump would have caused, or the reasons why this approach is necessary to unlock continuity for Zod's ecosystem libraries. They're quite subtle.

[0] https://github.com/colinhacks/zod/issues/4371


You can absolutely publish 3.x.x after 4.x.x, they just won’t be “latest” (of course).

Speaking bluntly, this isn’t how libraries in the npm upgrade, and pitching this does not inspire confidence.


Just would like to say I really appreciate your consideration of releasing breaking changes. I know not all libraries can employ your methodology but I wish many more of them would, as a frontend platform engineer.


The path of least short term pain is often the best path.

Everyone old in Python ecosystem remembers the Python 2/3 migration madness.


A heartfelt thank you for not breaking the world like many other libraries do!

Breaking changed in fundamental but auxiliary libraries are so painful in the world of frontend development that it's not even funny anymore.


Is there any interop between v3 and v4 schemas themselves? We have an enormous graph of hundreds (if not thousands) of Zod schemas, many inheriting or extending others. It's difficult to envisage us being able to do an incremental upgrade unless both versions can interact neatly.

But I am looking forward to seeing if this fixes our issue where in the worst case a 500 line schema file turns into 800,000 lines of generated types.


No, that kind of interop, especially static interop (assignability), would've been totally unworkable. Despite the length of the changelog, there are very few breaking changes to the user-facing API surface. It's mostly internal/structural changes and deprecations (most of which can be fixed with a find&replace).

Report back about that .d.ts issue. It should be far better. That kind of type explosion usually happens when TypeScript needs to infer function/method return types. Zod 4 uses isolatedDeclarations so this kind of thing shouldn't happen.


No. You will have errors if you try this.


While publishing both versions together helps with compatibility issues, the library now becomes even heavier in environments where tree-shaking is not enabled. In React Native, for example, you have to jump through a lot of hoops to get tree-shaking to work (including getting off the default metro bundler, which can often be a non-starter).


I saw your tweet about cracking type inference for recursive schemas. It seems like the solution to this problem is pretty straightforward and simple. Was the solution really as simple as using a getter, or is there some additional complexity that I am failing to grok?


the solution is to avoid TypeScript eagerly validating the object's value type. eager validation breaks cyclic references.


Zod's source code is a bit unwieldy to explain what's going on I feel, so here's a small validation library I wrote that uses the same principle

https://github.com/mary-ext/atcute/blob/596e023bcb490b16d09a...


Big thank you for the work on Zod - it's a fantastic library and it's been incredibly valuable for data validation/parsing in Node environments for me over the last two years.


I had so much difficulty with recursive types mixed with discriminated union (think xml in json for example) hopefully it'll be better now


this blog post was a delight to read. great work, colin && thanks!!


A perfectly valid evolution strategy is to add new APIs while keeping the old (which you may or may not choose to deprecate). That is, add the new APIs to 3.x (but probably call them something other than v4.

Now, that's effectively what 3.25 is. But there are some problematic extras... the semantics of the semantic versioning is muddled. That means confusion, which means some people will spin their wheels after not initially grokking the situation correctly. Also, there seems to be an implication there could be a strong deprecation of v3 APIs coming. That is, you have to wonder how long the window for incremental migration will remain open.

To be frank, I wouldn't touch zod on any project I hoped will be around for a while. Predicting the future is an uncertain business, but we can look at the past. In a few years, I think it's reasonable to guess zod v4 will be getting the same treatment zod v3 is getting now.

Not that I think you ought to do anything different. I probably wouldn't want to maintain some old API I came up with years ago indefinitely either (not without a decent support contract, that is).


Uhm... Wouldn't it make more sense just to publish a `zod4` package?


TLDR: What do I put on my package json to get latest latest latest? "^4" ?


These numbers don't reflect anything useful. This is the total size of the code in the package, most of which will be tree-shaken. In Zod's case, the package now contains three independent sub-libraries. I recommend plugging a script into bundlejs.com[0] to see bundle size numbers for a particular script

[0] https://bundlejs.com


Author here. I wrote a fairly detailed writeup here[0] for those who are interested in the reasons for this approach.

Ultimately you're right that npm doesn't work well to manage the situation Zod finds itself in. But Zod is subject to a bunch of constraints that virtually no other libraries are subject to. There are dozens or hundreds of libraries that directly import interfaces/classes from "zod" and use them in their own public-facing API.

Since these libraries are directly coupled to Zod, they would need to publish a new major version whenever Zod does. That's ultimately reasonable in isolation, but in Zod's case it would trigger a "version avalanche" would just be painful for everyone involved. Selfishly, I suspect it would result in a huge swath of the ecosystem pinning on v3 forever.

The approach I ended up using is analogous to what Golang does. In essence a given package never publishes new breaking versions: they just add a new subpath when a new breaking release is made. In the TypeScript ecosystem, this means libraries can configure a single peer dependency on zod@^3.25.0 and support both versions simultaneously by importing what they need from "zod/v3" and "zod/v4". It provides a nice opt-in incremental upgrade path for end-users of Zod too.

[0] https://github.com/colinhacks/zod/issues/4371


Are there any plans of doing an actual v4 release and making zod/v4 the default there? Perhaps make simulatenous releases of zod v3 containing a /v4 path and a zod v4 containing a /v3 path? Then converge on just zod v4 with no /v3 from 4.1 onwards.


Yep, at some indeterminate point when I gauge that there's sufficient support for Zod 4 in the ecosystem, I'll publish `zod@4.0.0` to npm. This is detailed in the writeup[0]

[0] https://github.com/colinhacks/zod/issues/4371


Is there any advantage to this approach over publishing a separate "zod4" package? That would be just as opt-in and incremental, not bloat download sizes forever, and make it easier to not accidentally import the wrong thing.


Ecosystem libraries would need to switch from a single peer dependency on Zod to two optional peer dependencies. Despite "optional peer dependencies" technicall being a thing, its functionally impossible for a library to determine which of the two packages your user is actually bringing to the table.

Let's say a library is trying to implement an `acceptSchema` function that can accepts `Zod3Type | Zod4Type`. For starters: those two interfaces won't both be available if Zod 3 and Zod 4 are in separate packages. So that's already a non-starter. And differentiating them at runtime requires knowing which package is installed, which is impossible in the general case (mostly because frontend bundlers generally have no affordance for optional peer dependencies).

I describe this in more detail here: https://x.com/colinhacks/status/1922101292849410256


Thanks a lot for zod and really looking forward to trying out the new version! Also as a primary Go developer this issue just has been really interesting to read about. Go avoids this issue by just not having peer dependencies and relying on the compiler to not bundle unused code - or just live with huge binaries when it doesn't work out :-)

I am still curious about the `zod4` idea though. Any thoughts on adding an `__api_version` type of property to distinguish on to all of zod's public types? Perhaps it's prohibitive code-size wise but wondering if it's possible somehow. Then downstream libraries could differentiate by doing the property check.

Just wanted to share the idea but the current state seems ok too.


Hopefully our careers in software work out because we will not be successful as actors. Actually Ashcon will be fine.


Indeed, he was definitely a natural :)


We are releasing a proper announcement later! This video was scheduled to premiere at 10am but due to some last minute bugs (and Murphy's Law) the build didn't finish in time. We decided not to delay the premiere because it was already on calendars and whatnot.


Haha, that's always how it works, it wouldn't be a real launch day with out a little bit of chaos ;)

Nice job on the video to you and the team, and congrats on the 1.0 launch!


This video is definitely corny. (I'm in it.) This is intended as an extremely accessible introduction to Bun, definitely not targeted at the HN crowd. I think we really just wanted to make something with some production value to reflect the fact that Bun itself is post-1.0 and more stable/polished.

The two camera thing is a classic trick that lets you add some visual interest. Basically you're cutting between two angles. And you can only be looking directly at one camera at a time so you won't be looking into the lens for one of the two shots.


In my opinion, this is because the angle is too small. Just add another 15-25° and it would be good.


Colin here, I work on Bun.

Bun 1.0 is actually still compiling but we decided not to delay the premiere of this announcement video since it was already on calendars. We'll be publishing a full announcement post later today that's more detailed than this video.


Report back to this thread with a link please, when the post / v1 is ready


https://bun.sh/blog/bun-v1.0

Edit: Sorry it went down again. Not sure if this will be the eventual link.


I'm keeping an eye on https://bun.sh/blog for the update


Me too


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: