npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gmod/cram

v14.1.1

Published

read CRAM files with pure Javascript

Readme

@gmod/cram

NPM version Build Status

Read CRAM files in node or the browser. Handles CRAM 2.x and 3.x, .crai indexes, and every v3 and v3.1 codec, fqzcomp and tok3 included. The codecs are the same htscodecs C that samtools uses, compiled to WebAssembly and inlined in the bundle, so there is nothing extra to serve or configure.

npm install @gmod/cram

Quick start

import { IndexedCramFile, CraiIndex } from '@gmod/cram'
import { IndexedFasta } from '@gmod/indexedfasta'

const fasta = new IndexedFasta({
  path: '/path/to/reference.fa',
  faiPath: '/path/to/reference.fa.fai',
})

const indexedFile = new IndexedCramFile({
  cramPath: '/path/to/file.cram',
  index: new CraiIndex({ path: '/path/to/file.cram.crai' }),
  fetchReferenceSequence: (seqId, start, end, refName) =>
    fasta.getSequence(refName, start, end),
})

const refId = await indexedFile.cram.getReferenceId('chr1')
const records = await indexedFile.getRecordsForRange(refId, 10000, 20000)

for (const record of records) {
  console.log(record.readName, record.start, record.getCigarString())
}

cramPath can also be cramUrl or cramFilehandle, and CraiIndex takes url or filehandle in place of path, so the same code runs in a browser.

Without a bundler

For <script>-tag consumers, igv.js among them, the package ships a standalone browser build that puts the same exports on window.gmodCRAM:

<script src="https://unpkg.com/@gmod/cram/dist/cram-bundle.js"></script>
<script>
  const { IndexedCramFile, CraiIndex } = window.gmodCRAM
</script>

The example directory has a working page.

Three things to know

Coordinates are 0-based half-open, the same as @gmod/bam. Add 1 when you convert back to a 1-based text format like SAM's POS. (This changed in v10 — see MIGRATION.md.)

References are numbers, not names. CRAM identifies a reference by the position of its @SQ line in the SAM header, and getRecordsForRange, record.sequenceId and the .crai all speak in those numbers. The order is whatever a given file says, so ask the file rather than hardcoding it:

await indexedFile.cram.getReferenceId('chr1') // 0, throws if there is no such SN
await indexedFile.cram.getReferenceName(0) // 'chr1'
await indexedFile.cram.getReferenceInfo() // [{ name, length, md5 }, ...]

The one id that is not an @SQ position is -1, an unplaced read.

You supply the reference sequence. CRAM stores reads as differences from a reference, so the library cannot give you bases without one — fetchReferenceSequence is the callback that supplies them. It receives both the seq id and the name, so a name-keyed source like IndexedFasta needs no lookup of its own. Without it you still get positions, CIGARs and the kind and length of every difference, just not the bases involved. The exception is a file that embeds its reference (samtools view --output-fmt-option embed_ref=1): it decodes fully with no callback, and its embedded bases win over the callback's.

What to ask a record

record.getReadBases() // 'ACGT…', the read sequence
record.getCigarString() // '50M2I48M'
record.getMismatches() // every difference from the reference
record.getTag('NM') // one auxiliary tag
record.qualityScoreAt(0) // one base quality
record.isReverseComplemented() // the usual SAM flags

getMismatches() is the intended way to see how a read differs from the reference. You should not need to know anything about how CRAM encodes it:

for (const m of record.getMismatches()) {
  console.log(
    String.fromCharCode(m.code), // 'X', 'I', 'D', 'N', 'S' or 'H'
    m.refPos, // 0-based reference position
    m.length, // reference bases covered (deletions, skips)
    m.bases, // substituted or inserted bases
    m.clipLength, // read bases consumed (insertions, clips)
  )
}

If you process enough records that per-difference objects start to matter, record.forEachMismatch(callback, opts?) reports the same differences without allocating, and takes an optional { start, end } window. The same pattern exists for the CIGAR (forEachCigarOp) and for tags and quality scores, stored in one array per slice rather than one per record. docs/api.md has all of it; docs/memory.md explains why they are structured that way.

Slices decode on a worker pool

A query decodes one or more slices, and slices are independent. Since 12.1 they decode on a shared pool of workers wherever the host has them — on by default, with nothing to configure, since the worker ships inlined like the wasm:

// already parallel
const records = await indexedFile.getRecordsForRange(refId, 10000, 20000)

In a browser that is worth 2.1-3.6x once a query touches four or more slices, and roughly break-even on the single-slice queries shallow files produce. Leave it on even if you already run this library inside your own worker — a worker is one thread, and the pool nested inside one is where those numbers were measured.

useSliceWorkerPool: false turns it off and numSliceWorkers sizes it. The reason to reach for either is a host that runs several worker contexts, since one pool serves a context rather than a machine. docs/workers.md has the measurements.

Reading over HTTP

An indexed query reads a file as one byte range per slice plus two small ones per container — a whole-reference query on a 141 KB test file issues 202 of them. A bare RemoteFile turns each one into its own range request, so put @gmod/range-cache-filehandle underneath:

import { RemoteFileWithRangeCache } from '@gmod/range-cache-filehandle'

const cram = new IndexedCramFile({
  cramFilehandle: new RemoteFileWithRangeCache(url),
  index: new CraiIndex({
    filehandle: new RemoteFileWithRangeCache(`${url}.crai`),
  }),
})

The cache serves those reads out of 256 KiB chunks, so neighboring reads share a request and the 546 become a handful. It also threads the AbortSignal below, so a canceled query stops the network fetch, not just the decode.

Cancelling a query

Pass an AbortSignal and the query stops decoding and drops the fetch it has in flight:

const controller = new AbortController()
const records = indexedFile.getRecordsForRange(0, 1000, 2000, {
  signal: controller.signal,
})
controller.abort() // `records` rejects with an AbortError

Aborting your query never fails a concurrent one, because decodes shared between queries are reference-counted. A query with no signal never stops, so it pins any slice it is waiting on for everyone. Thread the signal through consistently. docs/api.md has the details.

Docs

Academic use

Written with NHGRI funding as part of JBrowse. If you use this in a publication, please cite the most recent JBrowse paper at jbrowse.org.

License

MIT © Robert Buels