UNPKG

@gmod/vcf

Version:

High performance streaming Variant Call Format (VCF) parser in pure JavaScript

167 lines (126 loc) 4.14 kB
# vcf-js VCF (variant call format) parser [![NPM version](https://img.shields.io/npm/v/@gmod/vcf.svg?logo=npm&style=flat-square)](https://npmjs.org/package/@gmod/vcf) ![Build Status](https://img.shields.io/github/actions/workflow/status/GMOD/vcf-js/publish.yml?branch=main) ## Usage ```typescript import { TabixIndexedFile } from '@gmod/tabix' import VCF from '@gmod/vcf' const tbiIndexed = new TabixIndexedFile({ path: '/path/to/my.vcf.gz' }) const headerText = await tbiIndexed.getHeader() const parser = new VCF({ header: headerText }) // strict?: boolean (default true) const variants = [] await tbiIndexed.getLines('ctgA', 200, 300, line => variants.push(parser.parseLine(line)), ) ``` ## Variant `parseLine(line)` returns a `Variant` with these fields: ```typescript { CHROM: 'contigA', POS: 3000, ID: ['rs17883296'], REF: 'G', ALT: ['T', 'A'], QUAL: 100, FILTER: 'PASS', // 'PASS' | string[] of filter names | undefined if '.' INFO: { NS: [3], DP: [14], AF: [0.5], DB: true, // Flag type XYZ: ['5'], // unknown fields default to Number=1, Type=String }, } ``` INFO and FORMAT values are typed using header metadata. Values are arrays unless `Type=Flag`, in which case they are `true`. Fields defined in the [VCF spec](https://samtools.github.io/hts-specs/VCFv4.3.pdf) are typed even without a header entry. ### Sample methods - `variant.SAMPLES()` — full sample data with all FORMAT fields parsed - `variant.GENOTYPES()` — GT strings only (faster) - `variant.processGenotypes(callback)` — iterate genotypes without allocating strings (fastest) ```typescript let homRef = 0 variant.processGenotypes((str, start, end, sampleIdx) => { if ( end - start === 3 && // e.g. "0|0" str.charCodeAt(start) === 48 && // 48 = '0' str.charCodeAt(start + 2) === 48 ) { homRef++ } }) ``` Sample data is lazily parsed — nothing is computed until these methods are called. ## Metadata `parser.getMetadata(...keys)` returns header metadata, filtered by the keys provided: ```typescript parser.getMetadata('INFO', 'DP') // { Number: 1, Type: 'Integer', Description: 'Total Depth' } parser.getMetadata('INFO', 'DP', 'Number') // 1 ``` Call with no arguments to get all metadata. `parser.samples` lists sample names. ## Streaming To parse a plain VCF without tabix, collect header lines until the first non-header line, then construct the parser: ```typescript import fs from 'fs' import VCF from '@gmod/vcf' import { createGunzip } from 'zlib' import readline from 'readline' const rl = readline.createInterface({ input: fs.createReadStream('file.vcf.gz').pipe(createGunzip()), }) const header = [] let parser rl.on('line', line => { if (line.startsWith('#')) { header.push(line) } else { if (!parser) { parser = new VCF({ header: header.join('\n') }) } const variant = parser.parseLine(line) console.log(variant.CHROM, variant.POS) } }) ``` ## Breakends `parseBreakend(alt)` parses a breakend ALT string: ```typescript import { parseBreakend } from '@gmod/vcf' parseBreakend('C[2:321682[') // { MateDirection: 'right', Replacement: 'C', MatePosition: '2:321682', Join: 'right' } ``` All four bracket forms from the VCF spec: | ALT form | Join | MateDirection | | -------- | ----- | ------------- | | `t[p[` | right | right | | `t]p]` | right | left | | `[p[t` | left | right | | `]p]t` | left | left | - `Join` — whether the replacement base appears before (`right`) or after (`left`) the mate position - `MateDirection` — `[` means the mate sequence extends rightward; `]` means leftward ### Single breakends When the ALT starts or ends with `.`, `parseBreakend` returns `SingleBreakend: true` with no `MatePosition`: ```typescript parseBreakend('C.') // { Join: 'right', Replacement: 'C', SingleBreakend: true } parseBreakend('.ACGT') // { Join: 'left', Replacement: 'ACGT', SingleBreakend: true } ``` ## Publishing [Trusted publishing](https://docs.npmjs.com/about-trusted-publishing) via GitHub Actions. ```bash pnpm version patch # or minor/major ```