```ts
/**
 * Parses a semantic version string of the form MAJOR.MINOR.PATCH with an optional -prerelease and an optional +build,
 * returning the three numbers plus the prerelease and build strings, each null when absent. Throws Error if invalid input.
 */
export function parseSemver(version: string): { major: number; minor: number; patch: number; prerelease: string | null; build: string | null } {
    const parts = version.trim();
    let match;
    
    // Check for the 'v' prefix, but don't require it
    if (match = parts.match(/^v(.*))?/)) {
        versions = parts.slice(match.index);
        
        // Split into required and optional parts
        [major, minor, patch] = parseMajorMinorPatch(versions[0]);
        versions.shift();
        
        { 
            match = versions.shift();  // -prerelease?
            if (match && !match.startsWith('-')) {
                throw new Error(`Invalid version format: expected "-prerelease", but got '${match}'`);
            }
            prerelease = match ? match.slice(1) : null;
        } 
        {  
            match = versions.shift();  // +build?
            if (match && !match.startsWith('+')) {
                throw new Error(`Invalid version format: expected "+build", but got '${match}'`);
            }
            build = match ? match.slice(1) : null; 
        } 
    } else { 
        throw new Error(`Invalid version format: must start with "v" or be exactly empty for semantic version}`);
    }

    
    return {
        major,
        minor,
        patch,
        prerelease,
        build
    };
}
    
function parseMajorMinorPatch(version: string) : number[] { 
    const parsed = version.split('.').map(NumberFilter); // 0,1,3 for example, and reject any leading zeroes.
    if (!parsed.every(n => Number.isInteger(n))) {
        throw new Error(`Invalid major or minor version field: must be integers`);
    }
    return parsed; 
}
```