Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.
TypeScript
127
35 commits
updated Sep 18, 2026
Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.
SemVer and SemVerRange records.npm add verkit
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.
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.
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.
See the API reference.
parse, parseComparator, and parseRange throw detailed TypeErrors. Their
tryParse* wrappers return null; other safe transforms and predicates keep
their documented null/false behavior.
Only renamed or reshaped node-semver APIs are listed; same-named functions
such as clean, coerce, compare, and satisfies are omitted.
| node-semver | verkit |
|---|---|
SemVer | parse |
parse | tryParse |
valid | normalize |
inc, diff | increment, difference |
major, minor, patch, prerelease | getMajor, getMinor, getPatch, getPrerelease |
rcompare, compareLoose, cmp | compareReversed, compare with { loose: true }, compareWithOperator |
eq, neq, gt, gte, lt, lte | isEqual, isNotEqual, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual |
rsort | sortReversed |
rcompareIdentifiers | compareIdentifiersReversed |
Comparator | SemVerComparator, parseComparator, tryParseComparator |
| Comparator formatting, test, and intersection | normalizeComparator, satisfiesComparator, comparatorsIntersect |
Range | parseRange |
toComparators, validRange | rangeToComparators, normalizeRange |
maxSatisfying, minSatisfying, minVersion | findMaxSatisfying, findMinSatisfying, findMinimumForRange |
outside, gtr, ltr | isOutsideRange, isGreaterThanRange, isLessThanRange |
intersects, subset | rangesIntersect, isRangeSubset |
RELEASE_TYPES | INCREMENT_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.
verkit follows node-semver semantics with four user-visible differences:
SemVerRange objects retain their parse-time options. node-semver
helpers may reparse a Range from raw using call-site options.NODE_DEBUG=semver output.Full package imports, minified with Rolldown:
| Package | Minified | gzip | Brotli |
|---|---|---|---|
| verkit | 18,820 B | 5,914 B | 5,375 B |
| semver | 24,424 B | 7,427 B | 6,765 B |
| verkit reduction | 26.0% | 20.4% | 20.5% |
Common validation, range, comparison, increment, and coercion imports, tree-shaken and minified with Rolldown:
| Package | Minified | gzip | Brotli |
|---|---|---|---|
| verkit | 9,872 B | 3,401 B | 3,121 B |
| semver | 25,577 B | 7,500 B | 6,835 B |
| verkit reduction | 61.4% | 54.7% | 54.3% |
Run pnpm test:size to reproduce the comparison.
Measured on a MacBook Pro with an Apple M1 Max and 32 GB RAM. Higher is better.
| Operation | verkit ops/s | semver ops/s | Faster |
|---|---|---|---|
| Parse and normalize | 3.67M | 3.26M | verkit 1.13× |
| Compare | 3.09M | 2.36M | verkit 1.31× |
| Compare parsed versions | 40.59M | 28.62M | verkit 1.42× |
| Increment | 3.38M | 2.03M | verkit 1.66× |
| Coerce | 2.77M | 2.32M | verkit 1.19× |
| Satisfy uncached ranges | 148.5K | 122.3K | verkit 1.21× |
| Satisfy pre-parsed inputs | 22.91M | 7.20M | verkit 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.
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.
TypeScript
99.9%
Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.
TypeScript
127
35 commits
updated Sep 18, 2026
Fast, zero-dependency SemVer for ESM and TypeScript, with functional, tree-shakeable APIs.
SemVer and SemVerRange records.npm add verkit
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.
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.
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.
See the API reference.
parse, parseComparator, and parseRange throw detailed TypeErrors. Their
tryParse* wrappers return null; other safe transforms and predicates keep
their documented null/false behavior.
Only renamed or reshaped node-semver APIs are listed; same-named functions
such as clean, coerce, compare, and satisfies are omitted.
| node-semver | verkit |
|---|---|
SemVer | parse |
parse | tryParse |
valid | normalize |
inc, diff | increment, difference |
major, minor, patch, prerelease | getMajor, getMinor, getPatch, getPrerelease |
rcompare, compareLoose, cmp | compareReversed, compare with { loose: true }, compareWithOperator |
eq, neq, gt, gte, lt, lte | isEqual, isNotEqual, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual |
rsort | sortReversed |
rcompareIdentifiers | compareIdentifiersReversed |
Comparator | SemVerComparator, parseComparator, tryParseComparator |
| Comparator formatting, test, and intersection | normalizeComparator, satisfiesComparator, comparatorsIntersect |
Range | parseRange |
toComparators, validRange | rangeToComparators, normalizeRange |
maxSatisfying, minSatisfying, minVersion | findMaxSatisfying, findMinSatisfying, findMinimumForRange |
outside, gtr, ltr | isOutsideRange, isGreaterThanRange, isLessThanRange |
intersects, subset | rangesIntersect, isRangeSubset |
RELEASE_TYPES | INCREMENT_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.
verkit follows node-semver semantics with four user-visible differences:
SemVerRange objects retain their parse-time options. node-semver
helpers may reparse a Range from raw using call-site options.NODE_DEBUG=semver output.Full package imports, minified with Rolldown:
| Package | Minified | gzip | Brotli |
|---|---|---|---|
| verkit | 18,820 B | 5,914 B | 5,375 B |
| semver | 24,424 B | 7,427 B | 6,765 B |
| verkit reduction | 26.0% | 20.4% | 20.5% |
Common validation, range, comparison, increment, and coercion imports, tree-shaken and minified with Rolldown:
| Package | Minified | gzip | Brotli |
|---|---|---|---|
| verkit | 9,872 B | 3,401 B | 3,121 B |
| semver | 25,577 B | 7,500 B | 6,835 B |
| verkit reduction | 61.4% | 54.7% | 54.3% |
Run pnpm test:size to reproduce the comparison.
Measured on a MacBook Pro with an Apple M1 Max and 32 GB RAM. Higher is better.
| Operation | verkit ops/s | semver ops/s | Faster |
|---|---|---|---|
| Parse and normalize | 3.67M | 3.26M | verkit 1.13× |
| Compare | 3.09M | 2.36M | verkit 1.31× |
| Compare parsed versions | 40.59M | 28.62M | verkit 1.42× |
| Increment | 3.38M | 2.03M | verkit 1.66× |
| Coerce | 2.77M | 2.32M | verkit 1.19× |
| Satisfy uncached ranges | 148.5K | 122.3K | verkit 1.21× |
| Satisfy pre-parsed inputs | 22.91M | 7.20M | verkit 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.
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.
TypeScript
99.9%