Symbols in package.json

June 06, 2022

Recently, I came across a dependency error. Basically, a package being used by a project didn't have the correct version of a dependency listed as a peer.

{
	// package that had the issue
	"peerDependencies": {
		"package-a": "^6.5.7"
	}
}

{
	// project that used the said package
	"dependencies": {
		"package-a": "~6.6.7"
	}
}

In this post I shall try to explain what symbols like ~ ^ and those numbers mean?

Talking about the numbers first

I intially found it a bit weird that there are two decimals in this version string. But, now, I know better. Each of those digits represents a change in the version. Depending upon the position of the digit that was incremented, the change can be divided into:

  • Major - A Breaking Change or a New feature
  • Minor - A change that maintains backwards compatiblity
  • Patch - A minor bug/error fix

Turns out that there is a whole set of rules defining what these digits are and under what circumstances should and which digit be incremented. This rule set is referred to as Semantic Versioning. So, for a package with version string 8.5.4:

  • 8 => Major Version
  • 5 => Minor Version. Like: 5th revision of version 8
  • 4 => Patch Version. Like: 4th revision of the 5th revision (of version 8)

And now if a typo was fixed then, the digit representing "patch" would be incremented making the new version string 8.5.(4 + 1) = 8.5.5.

In a proper production level released package, the minimum "major" is 1 and minor, patch range from 0 to 9. (The first release is usually 1.0.0).

On the other hand, for a package that is released for development purposes, usually has the "major" as 0 (and possibly no limit on range of major and minor).

Talking about the symbols

Before I jump on symbols, I shall like to give a brief on what package.json stores.

What exactly does package.json store?

A package.json file basically lists all the "pieces of code" (aka dependencies) that the current project (which itself is a piece of code) may be dependent upon. Along with this, it stores what version of these "pieces of code" should actually be used.

This version requirement can be:

  • strict: Only v1 of package-a
  • a little less strict: Either v2.3 or v3.1 of package-a
  • flexible: Anything after v3 of package-a will do fine.

How do symbols help?

Symbols like: ~, ^, <, || basically represents the above requirement in a compact manner.

  • < aka less-than

    • Corresponds to major, minor, patch
    • <4.3.0 means any major or minor or patch update less than 4.3.0
      • 4.2.9 or 3.1.7 would work
  • > aka greater-than

    • Corresponds to major, minor, patch
    • >4.3.0 means any major or minor or patch update greater than 4.3.0
      • 5.1.6 would work but 4.2.9 won't

This is where it gets slightly tricky

The following symbols can allow updates to major, minor or patch depending upon which of them is defined.

~4.3.5 means a totally different thing than ~4 and ^7.8.6 is different from ^7.0.6.

  • ~ aka tilde

    • Allows patch update if a minor version is defined or a minor update if no patch version is defined
    • For ~4.3.5
      • Only patch updates would be allowed
      • Greater than or equal to 4.3.5 but, less than 4.4.0
      • ~4.3.6 would work but, ~4.4.1 won't work
    • For ~4
      • Only minor and patch updates would be allowed
      • Greater than or equal to 4.0.0 but, less than 5.0.0
      • 4.4.2 would work but, 5.0.0 won't work
  • ^ aka carret

    • Allows updates less than the left most non-zero digit
    • If major is zero and minor is a non-zero number then, only patch updates would be allowed
    • If major is non zero and minor is zero then both minor and patch updates would be allowed
    • For ^0.3.5
      • Greater than or equal to 0.3.5 but, less than 0.4.0
      • 0.3.6 or 0.3.9 would work but, 0.4.x won't work
    • For ^4.0.5
      • Greater than or equal to 4.0.5 but, less than 5.0.0
      • 4.1.5 would work but, 5.x.y won't work

Wrapping Up

This post obviously doesn't contain explanation for all the combantions possible with all the relevant symbols. Instead, it is just an attempt to better explain the concepts that confused me.

In a later post, I shall be sharing some actual examples using node-semver, which is the tool that npm uses to parse these Semantic Versioning Complaint dependencies version.