Introducing SemVer Prime!
Thu Jun 20 2024SemVer Prime1 is one of my attempts at fixing SemVer. It's meant to get you to think a bit more about your versioning. If you'd like more freedom, check out Humane SemVer!
SemVer Prime, or Gödel's SemVer
SemVer Prime is a new way of doing SemVer, which puts more SemVers in your SemVer!
It works using prime numbers and a bit of madness; a bit like Gödel's encoding, really. But an interactive demo is worth a thousand words:
Category | Major | Minor | Patch |
API | 2 | 2 | 2 |
ABI | 3 | 3 | 3 |
Network | 5 | 5 | 5 |
Global Version | 2 | 15 | 1 |
Hmm... Yes I see, this version number is made of version numbers!
Exactly! Specifically, the version number is computed through the following math: each dimension dim
of our versioning (API, ABI, Networking Protocol...) is assigned a prime number pdim
.
Each section of the global semver is then computed with the following formula:
Vglobal = Πdim(pdimVdim)
The beauty of this formula is that it has a few very convenient properties:
- It's bijective: provided that you know which prime goes with which dimension, factorizing
Vglobal
gives you each dimension's versionVdim
. In fact, you can divide a new version by the previous one to deduce which dimensions have changed. - It's monotonic: increasing any component of a dimension's version will increase the global version. This makes SemVer Prime a compliant subset of SemVer!
- It starts at
1.1.1
, meaning you've already outgrown the0.x.y
trap the moment you start using SemVer Prime! This also means that you can transition from the trap to SemVer Prime whenever you decide to graduate fromMAJOR=0
. - On that note, SemVer Prime is purely descriptive: one dimension's version being at
0.x.y
doesn't mean it's unstable, it just means there hasn't been a breaking change in it since it started getting tracked. - SemVer Prime allows you to change which prime is assigned to which dimension in two cases:
- The prime was assigned to a dimension whose version was
0.0.0
, and so was its new assigned dimension's version. - When upgrading the "Versioning"'s dimension (something we'll discuss later), although doing so is discouraged as it can make decoding your versions confusing.
- The prime was assigned to a dimension whose version was
But ultimately, the true beauty in SemVer Prime is the fact that your version number can now have dimensions. This means that it can now communicate what is still compatible, instead of just telling the user that two versions have a given level of global compatibility.
This can be even more precious for projects that want to keep orthogonal sub-projects' version synced: just add one dimension per subproject!
But won't the numbers get very big?
They will! :D
Oh, you mean it as a bad thing? Well, that is SemVer Prime's weak point.
More specifically, it may lead to issues with SemVer-based tooling that parses each number as a bounded integer (which is most of them).
According to a bit of googling I've done, you should generally not expect SemVer tooling to play nice with numbers above JS's MAX_SAFE_INTEGER
, i.e. 253 - 1
. That does give you a bit of wiggle room (even though the global version grows exponentially).
Soo... back to square one?
Not so fast! Notice that "New Dimension" in the interactive tool? Well that's our escape hatch! When you reach an overflow on one of your dimension, you can follow this protocol:
- Release a transition version: this version bumps the "Versioning" dimension's major, adding it to the schema if necessary, while resetting every other dimension to
0.0.0
. This transition version should be identical to the latest version of the previous "Versioning" version, and users should be informed of that. - The next release can then bump the appropriate version dimensions as would normally be done.
This transition version is SemVer-legal thanks to the fact that versions with differing majors are never expected to be compatible, but are allowed to. This means that seeing the major decrease, while surprising, is compliant thanks to the new number being guaranteed unique.
One can argue that this breaks the "bigger number = more recent" aspect of versioning that's usually expected of a version numbering system, and it's a very fair critique. However, due to the exponential growth of SemVer Prime's values, I tend to think of it as "not something a human should interact with in its 'compressed' form": comparing 10-ish digit numbers is surprisingly hard!
As such, SemVer Prime is mostly designed to allow compressing a set of versions into a single version number that interacts well with SemVer-based tools... Except for the ones that tell you which version is the "latest"2...
Ok! I'm sold! How do I actually use this
You should start by defining your "key": the ordered list of dimensions you'll be keeping track of. You can always add more dimensions later, so you don't need to overthink it too much.
You should sort your key such that versions that are likely to change more often get assigned smaller primes. For example, if your library is a networking protocol, you'll probably want "Network" to be near the end of your key, as this is usually something we avoid making breaking changes to as upgrading requires a full redeployment for users3.
If your project has a set of "official" bindings, you may want to version each of them independently by adding one dimension per supported language familly; placing your higher support tiers at the begining of the list.
You should advertise that you're using this specific way of versioning to your users, ideally giving them an easy way to obtain the multi-dimension equivalent of your current version4.
Conclusion
While it is a lot more complicated than Humane SemVer, SemVer Prime has the quality of allowing users to identify what changes have been enacted on their dependencies purely through version numbering alone. In fact, dividing a new version by the one you currently use (adding 1 to each before doing that) gives you a list of the dimensions that have changed!
I find this property awesome, and will personally endeavour to push projects I have a voice in to transition to either SemVer Prime if they could also benefit from its mutli-dimensionality, or to Humane SemVer if they don't.
And on that note: at the time of writing, stabby
's latest release is 5.1.0
, which will be its last non-SemVer Prime version, as I would like to start keeping track of its API and ABI separately.
I leave you with a more convenient (and two-way) SemVer Prime translation tool:
Usage (describing each text area from left to right):
- Split versions: a linebreak-separated list of
dimension = "x.y.z"
, editing it will refresh the global version. - Key: a comma-separated list of dimension names; each dimension is assigned a prime corresponding to its position. Editing it will refresh the global version.
- Global version: Must be
global = "x.y.z"
, editing it will refresh the split versions.