Implement a TypeScript Semantic Versioning Type System
This challenge requires you to design and implement a robust TypeScript type system for semantic versioning (SemVer). A well-defined SemVer type system will improve code quality, catch common errors at compile time, and enhance developer understanding of version compatibility.
Problem Description
You need to create a set of TypeScript types that accurately represent and enforce the rules of Semantic Versioning (SemVer) as defined by the SemVer 2.0.0 specification. This includes defining types for:
- Core Version Components: Major, Minor, and Patch versions, which must be non-negative integers.
- Pre-release Identifiers: Optional identifiers like
alpha,beta,rc, followed by numbers. - Build Metadata: Optional identifiers that do not affect version precedence.
- Valid SemVer Strings: A type that can represent any valid SemVer string and can be parsed into its constituent parts.
- Version Comparison: Mechanisms to compare two SemVer versions and determine their precedence.
Key Requirements:
- Type Safety: Ensure that invalid SemVer combinations are caught by the TypeScript compiler.
- Immutability: Versions should be treated as immutable once created.
- Parsing: A function to parse a string into a structured SemVer object.
- Stringification: A function to convert a structured SemVer object back into its canonical string representation.
- Precedence Comparison: A function to compare two SemVer versions and return an indicator of their relationship (e.g., less than, equal to, greater than).
- Clear Type Definitions: Define distinct types for different parts of a SemVer version (e.g.,
MajorVersion,MinorVersion,PatchVersion,PreReleaseIdentifier,BuildMetadataIdentifier).
Expected Behavior:
- A valid SemVer string should be parsable without errors.
- An invalid SemVer string should either fail to parse or be rejected by the type system.
- The precedence comparison should follow the SemVer 2.0.0 rules precisely.
- TypeScript should prevent nonsensical assignments, e.g., assigning a string that doesn't conform to SemVer to a type expecting a SemVer object.
Edge Cases:
- Versions with no pre-release or build metadata.
- Versions with single-digit and multi-digit pre-release identifiers.
- Leading zeros in numeric identifiers (SemVer disallows this for core versions, but it's important to handle or reject).
- Empty pre-release or build metadata identifiers.
Examples
Example 1: Basic Version Parsing and Stringification
// Input
const versionString = "1.2.3";
// Expected Output (conceptually, based on your type definitions)
// {
// major: 1,
// minor: 2,
// patch: 3,
// preRelease: [],
// build: []
// }
// Parsed string: "1.2.3"
// Explanation: A simple version string with no pre-release or build metadata is parsed into its core components and stringified back to its original form.
Example 2: Version with Pre-release and Build Metadata
// Input
const versionString = "2.0.0-alpha.1+build.123";
// Expected Output (conceptually)
// {
// major: 2,
// minor: 0,
// patch: 0,
// preRelease: ["alpha", 1],
// build: ["build", 123]
// }
// Parsed string: "2.0.0-alpha.1+build.123"
// Explanation: A more complex version string including pre-release identifiers and build metadata is correctly parsed and stringified.
Example 3: Version Precedence Comparison
// Input
const versionA = "1.0.0";
const versionB = "1.0.0-alpha.1";
const versionC = "1.0.1";
const versionD = "2.0.0";
// Expected Output (conceptually for comparison functions)
// compare(versionA, versionB) -> greater than (1)
// compare(versionB, versionA) -> less than (-1)
// compare(versionA, versionA) -> equal to (0)
// compare(versionA, versionC) -> less than (-1)
// compare(versionC, versionD) -> less than (-1)
// Explanation: Demonstrates how different versions are ordered according to SemVer precedence rules. Pre-release versions are lower than normal versions.
Constraints
- All core version components (major, minor, patch) must be non-negative integers.
- Pre-release and build metadata identifiers can be alphanumeric strings or numbers.
- Numeric identifiers within pre-release and build metadata should be treated as numbers where appropriate for precedence.
- Leading zeros in numeric identifiers for major, minor, and patch versions are invalid SemVer and should be rejected or handled according to strict SemVer parsing.
- Your solution must be implementable entirely within TypeScript, leveraging its type system features.
- Performance is not a primary concern for this challenge, but overly inefficient parsing or comparison logic should be avoided.
Notes
- Refer to the SemVer 2.0.0 specification for detailed rules on versioning, pre-release identifiers, build metadata, and precedence.
- Consider using branded types or intersection types in TypeScript to enforce SemVer constraints at the type level.
- Think about how you will represent the different parts of a SemVer version (e.g., using union types, discriminated unions, or interfaces).
- The challenge is to build the type system and the associated runtime logic to support it. You should aim for a solution where the types themselves provide significant compile-time validation.