sxzz/verkit

Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.

TypeScript

127

35 commits

updated Sep 18, 2026

See the code

README

verkit

Open on npmx npm downloads Unit Test Codecov

Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.

Features

  • ✅ Complete SemVer version and range toolkit.
  • 🚀 Faster than node-semver across tested operations.
  • 📦 Pure ESM with zero runtime dependencies.
  • 💙 First-class TypeScript declarations.
  • 🌳 Functional, tree-shakeable named exports.
  • 🔁 Mutable SemVer and SemVerRange records.
  • ⚡ 26.0% smaller for full CDN imports.
  • 🪶 61.4% smaller with common bundled imports.
  • 🛡️ Immutable collection operations.

Install

npm add verkit

Versions

import {
  coerce,
  increment,
  normalize,
  normalizeFull,
  parse,
  truncate,
} from 'verkit'

const version = parse('1.2.3-rc.1+sha.abc')
const coerced = coerce('release 42.6.7.9', { rtl: true })
version.patch = 4

normalizeFull(version) // '1.2.4-rc.1+sha.abc'
normalize(version) // '1.2.4-rc.1'
increment(version, 'minor') // '1.3.0'
truncate(version, 'patch') // '1.2.4'
coerced?.major // 6

Version APIs accept strings or mutable SemVer objects returned by parse or coerce. normalizeFull keeps build metadata; normalized, incremented, and truncated versions omit it.

Comparison

import { compare, compareBuild, isGreaterThan, sortReversed } from 'verkit'

compare('1.0.0+one', '1.0.0+two') // 0
compareBuild('1.0.0+one', '1.0.0+two') // -1
isGreaterThan('2.0.0', '1.0.0') // true
sortReversed(['1.0.0', '2.0.0']) // ['2.0.0', '1.0.0']

compare ignores build metadata; compareBuild uses it as a tie-breaker.

Ranges

import {
  findMaxSatisfying,
  normalizeRange,
  parseRange,
  satisfies,
} from 'verkit'

const range = parseRange('^1.2.3')

normalizeRange(range) // '>=1.2.3 <2.0.0-0'
satisfies('1.5.0', range) // true
findMaxSatisfying(['1.2.3', '1.5.0', '2.0.0'], range) // '1.5.0'

Range APIs accept strings or mutable SemVerRange objects. They support comparators, unions, hyphens, wildcards, tilde, caret, loose parsing, and prereleases.

Range options are fixed when a SemVerRange is created. APIs that receive a parsed range use its stored options and do not accept another options argument:

const prereleases = parseRange('1.x', { includePrerelease: true })

satisfies('1.0.0-rc.1', prereleases) // true

To use different options, pass the original range string again or create another parsed range.

API

See the API reference.

Invalid input behavior

parse, parseComparator, and parseRange throw detailed TypeErrors. Their tryParse* wrappers return null; other safe transforms and predicates keep their documented null/false behavior.

Migrating from node-semver

Only renamed or reshaped node-semver APIs are listed; same-named functions such as clean, coerce, compare, and satisfies are omitted.

node-semververkit
SemVerparse
parsetryParse
validnormalize
inc, diffincrement, difference
major, minor, patch, prereleasegetMajor, getMinor, getPatch, getPrerelease
rcompare, compareLoose, cmpcompareReversed, compare with { loose: true }, compareWithOperator
eq, neq, gt, gte, lt, lteisEqual, isNotEqual, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual
rsortsortReversed
rcompareIdentifierscompareIdentifiersReversed
ComparatorSemVerComparator, parseComparator, tryParseComparator
Comparator formatting, test, and intersectionnormalizeComparator, satisfiesComparator, comparatorsIntersect
RangeparseRange
toComparators, validRangerangeToComparators, normalizeRange
maxSatisfying, minSatisfying, minVersionfindMaxSatisfying, findMinSatisfying, findMinimumForRange
outside, gtr, ltrisOutsideRange, isGreaterThanRange, isLessThanRange
intersects, subsetrangesIntersect, isRangeSubset
RELEASE_TYPESINCREMENT_TYPES (also includes release)

valid returns a normalized string | null in node-semver, so its equivalent is normalize. Use isValid when you only need a boolean.

Use options objects such as { loose: true } and { identifier, identifierBase }. Range options belong to the string-parsing step; parsed SemVerRange objects already contain them.

Differences from node-semver

verkit follows node-semver semantics with four user-visible differences:

  • Array helpers never mutate their inputs.
  • Parsed SemVerRange objects retain their parse-time options. node-semver helpers may reparse a Range from raw using call-site options.
  • verkit is ESM-only, with no CommonJS, CLI, or NODE_DEBUG=semver output.
  • Error text, stack traces, and supported runtimes may differ.

Bundle size

Full package imports, minified with Rolldown:

PackageMinifiedgzipBrotli
verkit18,820 B5,914 B5,375 B
semver24,424 B7,427 B6,765 B
verkit reduction26.0%20.4%20.5%

Common validation, range, comparison, increment, and coercion imports, tree-shaken and minified with Rolldown:

PackageMinifiedgzipBrotli
verkit9,872 B3,401 B3,121 B
semver25,577 B7,500 B6,835 B
verkit reduction61.4%54.7%54.3%

Run pnpm test:size to reproduce the comparison.

Benchmarks

Measured on a MacBook Pro with an Apple M1 Max and 32 GB RAM. Higher is better.

Operationverkit ops/ssemver ops/sFaster
Parse and normalize3.67M3.26Mverkit 1.13×
Compare3.09M2.36Mverkit 1.31×
Compare parsed versions40.59M28.62Mverkit 1.42×
Increment3.38M2.03Mverkit 1.66×
Coerce2.77M2.32Mverkit 1.19×
Satisfy uncached ranges148.5K122.3Kverkit 1.21×
Satisfy pre-parsed inputs22.91M7.20Mverkit 3.18×

Range benchmarks either cycle through 1,001 inputs to avoid cache hits or parse once and reuse the resulting objects.

Run runtime benchmarks with pnpm bench.

Sponsors

Sponsors

License

MIT © 2026-PRESENT Kevin Deng.

Parts of the implementation and test fixtures are derived from node-semver under the ISC license; see THIRD_PARTY_NOTICES.md.

esm
functional
immutable
range
semantic-versioning
semver
typescript
version

Contributors

sxzz

31 commits

gameroman

3 commits

binggao1230

1 commits

sxzz/verkit

Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.

TypeScript

127

35 commits

updated Sep 18, 2026

See the code

README

verkit

Open on npmx npm downloads Unit Test Codecov

Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.

Features

  • ✅ Complete SemVer version and range toolkit.
  • 🚀 Faster than node-semver across tested operations.
  • 📦 Pure ESM with zero runtime dependencies.
  • 💙 First-class TypeScript declarations.
  • 🌳 Functional, tree-shakeable named exports.
  • 🔁 Mutable SemVer and SemVerRange records.
  • ⚡ 26.0% smaller for full CDN imports.
  • 🪶 61.4% smaller with common bundled imports.
  • 🛡️ Immutable collection operations.

Install

npm add verkit

Versions

import {
  coerce,
  increment,
  normalize,
  normalizeFull,
  parse,
  truncate,
} from 'verkit'

const version = parse('1.2.3-rc.1+sha.abc')
const coerced = coerce('release 42.6.7.9', { rtl: true })
version.patch = 4

normalizeFull(version) // '1.2.4-rc.1+sha.abc'
normalize(version) // '1.2.4-rc.1'
increment(version, 'minor') // '1.3.0'
truncate(version, 'patch') // '1.2.4'
coerced?.major // 6

Version APIs accept strings or mutable SemVer objects returned by parse or coerce. normalizeFull keeps build metadata; normalized, incremented, and truncated versions omit it.

Comparison

import { compare, compareBuild, isGreaterThan, sortReversed } from 'verkit'

compare('1.0.0+one', '1.0.0+two') // 0
compareBuild('1.0.0+one', '1.0.0+two') // -1
isGreaterThan('2.0.0', '1.0.0') // true
sortReversed(['1.0.0', '2.0.0']) // ['2.0.0', '1.0.0']

compare ignores build metadata; compareBuild uses it as a tie-breaker.

Ranges

import {
  findMaxSatisfying,
  normalizeRange,
  parseRange,
  satisfies,
} from 'verkit'

const range = parseRange('^1.2.3')

normalizeRange(range) // '>=1.2.3 <2.0.0-0'
satisfies('1.5.0', range) // true
findMaxSatisfying(['1.2.3', '1.5.0', '2.0.0'], range) // '1.5.0'

Range APIs accept strings or mutable SemVerRange objects. They support comparators, unions, hyphens, wildcards, tilde, caret, loose parsing, and prereleases.

Range options are fixed when a SemVerRange is created. APIs that receive a parsed range use its stored options and do not accept another options argument:

const prereleases = parseRange('1.x', { includePrerelease: true })

satisfies('1.0.0-rc.1', prereleases) // true

To use different options, pass the original range string again or create another parsed range.

API

See the API reference.

Invalid input behavior

parse, parseComparator, and parseRange throw detailed TypeErrors. Their tryParse* wrappers return null; other safe transforms and predicates keep their documented null/false behavior.

Migrating from node-semver

Only renamed or reshaped node-semver APIs are listed; same-named functions such as clean, coerce, compare, and satisfies are omitted.

node-semververkit
SemVerparse
parsetryParse
validnormalize
inc, diffincrement, difference
major, minor, patch, prereleasegetMajor, getMinor, getPatch, getPrerelease
rcompare, compareLoose, cmpcompareReversed, compare with { loose: true }, compareWithOperator
eq, neq, gt, gte, lt, lteisEqual, isNotEqual, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual
rsortsortReversed
rcompareIdentifierscompareIdentifiersReversed
ComparatorSemVerComparator, parseComparator, tryParseComparator
Comparator formatting, test, and intersectionnormalizeComparator, satisfiesComparator, comparatorsIntersect
RangeparseRange
toComparators, validRangerangeToComparators, normalizeRange
maxSatisfying, minSatisfying, minVersionfindMaxSatisfying, findMinSatisfying, findMinimumForRange
outside, gtr, ltrisOutsideRange, isGreaterThanRange, isLessThanRange
intersects, subsetrangesIntersect, isRangeSubset
RELEASE_TYPESINCREMENT_TYPES (also includes release)

valid returns a normalized string | null in node-semver, so its equivalent is normalize. Use isValid when you only need a boolean.

Use options objects such as { loose: true } and { identifier, identifierBase }. Range options belong to the string-parsing step; parsed SemVerRange objects already contain them.

Differences from node-semver

verkit follows node-semver semantics with four user-visible differences:

  • Array helpers never mutate their inputs.
  • Parsed SemVerRange objects retain their parse-time options. node-semver helpers may reparse a Range from raw using call-site options.
  • verkit is ESM-only, with no CommonJS, CLI, or NODE_DEBUG=semver output.
  • Error text, stack traces, and supported runtimes may differ.

Bundle size

Full package imports, minified with Rolldown:

PackageMinifiedgzipBrotli
verkit18,820 B5,914 B5,375 B
semver24,424 B7,427 B6,765 B
verkit reduction26.0%20.4%20.5%

Common validation, range, comparison, increment, and coercion imports, tree-shaken and minified with Rolldown:

PackageMinifiedgzipBrotli
verkit9,872 B3,401 B3,121 B
semver25,577 B7,500 B6,835 B
verkit reduction61.4%54.7%54.3%

Run pnpm test:size to reproduce the comparison.

Benchmarks

Measured on a MacBook Pro with an Apple M1 Max and 32 GB RAM. Higher is better.

Operationverkit ops/ssemver ops/sFaster
Parse and normalize3.67M3.26Mverkit 1.13×
Compare3.09M2.36Mverkit 1.31×
Compare parsed versions40.59M28.62Mverkit 1.42×
Increment3.38M2.03Mverkit 1.66×
Coerce2.77M2.32Mverkit 1.19×
Satisfy uncached ranges148.5K122.3Kverkit 1.21×
Satisfy pre-parsed inputs22.91M7.20Mverkit 3.18×

Range benchmarks either cycle through 1,001 inputs to avoid cache hits or parse once and reuse the resulting objects.

Run runtime benchmarks with pnpm bench.

Sponsors

Sponsors

License

MIT © 2026-PRESENT Kevin Deng.

Parts of the implementation and test fixtures are derived from node-semver under the ISC license; see THIRD_PARTY_NOTICES.md.

esm
functional
immutable
range
semantic-versioning
semver
typescript
version

Contributors

sxzz

31 commits

gameroman

3 commits

binggao1230

1 commits

Languages

TypeScript

99.9%