@gmod/tabix 1.4.4 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/csi.js ADDED
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ const long_1 = __importDefault(require("long"));
26
+ const bgzf_filehandle_1 = require("@gmod/bgzf-filehandle");
27
+ const virtualOffset_1 = __importStar(require("./virtualOffset"));
28
+ const chunk_1 = __importDefault(require("./chunk"));
29
+ const util_1 = require("./util");
30
+ const indexFile_1 = __importDefault(require("./indexFile"));
31
+ const CSI1_MAGIC = 21582659; // CSI\1
32
+ const CSI2_MAGIC = 38359875; // CSI\2
33
+ function lshift(num, bits) {
34
+ return num * 2 ** bits;
35
+ }
36
+ function rshift(num, bits) {
37
+ return Math.floor(num / 2 ** bits);
38
+ }
39
+ class CSI extends indexFile_1.default {
40
+ constructor(args) {
41
+ super(args);
42
+ this.maxBinNumber = 0;
43
+ this.depth = 0;
44
+ this.minShift = 0;
45
+ }
46
+ async lineCount(refName, opts = {}) {
47
+ const indexData = await this.parse(opts);
48
+ if (!indexData) {
49
+ return -1;
50
+ }
51
+ const refId = indexData.refNameToId[refName];
52
+ const idx = indexData.indices[refId];
53
+ if (!idx) {
54
+ return -1;
55
+ }
56
+ const { stats } = indexData.indices[refId];
57
+ if (stats) {
58
+ return stats.lineCount;
59
+ }
60
+ return -1;
61
+ }
62
+ async indexCov() {
63
+ throw new Error('CSI indexes do not support indexcov');
64
+ return [];
65
+ }
66
+ parseAuxData(bytes, offset, auxLength) {
67
+ if (auxLength < 30) {
68
+ return {
69
+ refIdToName: [],
70
+ refNameToId: {},
71
+ };
72
+ }
73
+ const formatFlags = bytes.readInt32LE(offset);
74
+ const coordinateType = formatFlags & 0x10000 ? 'zero-based-half-open' : '1-based-closed';
75
+ const format = { 0: 'generic', 1: 'SAM', 2: 'VCF' }[formatFlags & 0xf];
76
+ if (!format) {
77
+ throw new Error(`invalid Tabix preset format flags ${formatFlags}`);
78
+ }
79
+ const columnNumbers = {
80
+ ref: bytes.readInt32LE(offset + 4),
81
+ start: bytes.readInt32LE(offset + 8),
82
+ end: bytes.readInt32LE(offset + 12),
83
+ };
84
+ const metaValue = bytes.readInt32LE(offset + 16);
85
+ const metaChar = metaValue ? String.fromCharCode(metaValue) : '';
86
+ const skipLines = bytes.readInt32LE(offset + 20);
87
+ const nameSectionLength = bytes.readInt32LE(offset + 24);
88
+ const { refIdToName, refNameToId } = this._parseNameBytes(bytes.slice(offset + 28, offset + 28 + nameSectionLength));
89
+ return {
90
+ refIdToName,
91
+ refNameToId,
92
+ skipLines,
93
+ metaChar,
94
+ columnNumbers,
95
+ format,
96
+ coordinateType,
97
+ };
98
+ }
99
+ _parseNameBytes(namesBytes) {
100
+ let currRefId = 0;
101
+ let currNameStart = 0;
102
+ const refIdToName = [];
103
+ const refNameToId = {};
104
+ for (let i = 0; i < namesBytes.length; i += 1) {
105
+ if (!namesBytes[i]) {
106
+ if (currNameStart < i) {
107
+ let refName = namesBytes.toString('utf8', currNameStart, i);
108
+ refName = this.renameRefSeq(refName);
109
+ refIdToName[currRefId] = refName;
110
+ refNameToId[refName] = currRefId;
111
+ }
112
+ currNameStart = i + 1;
113
+ currRefId += 1;
114
+ }
115
+ }
116
+ return { refNameToId, refIdToName };
117
+ }
118
+ // fetch and parse the index
119
+ async _parse(opts = {}) {
120
+ const bytes = await (0, bgzf_filehandle_1.unzip)((await this.filehandle.readFile(opts)));
121
+ // check TBI magic numbers
122
+ let csiVersion;
123
+ if (bytes.readUInt32LE(0) === CSI1_MAGIC) {
124
+ csiVersion = 1;
125
+ }
126
+ else if (bytes.readUInt32LE(0) === CSI2_MAGIC) {
127
+ csiVersion = 2;
128
+ }
129
+ else {
130
+ throw new Error('Not a CSI file');
131
+ // TODO: do we need to support big-endian CSI files?
132
+ }
133
+ this.minShift = bytes.readInt32LE(4);
134
+ this.depth = bytes.readInt32LE(8);
135
+ this.maxBinNumber = ((1 << ((this.depth + 1) * 3)) - 1) / 7;
136
+ const maxRefLength = 2 ** (this.minShift + this.depth * 3);
137
+ const auxLength = bytes.readInt32LE(12);
138
+ let aux = {
139
+ refIdToName: [],
140
+ refNameToId: {},
141
+ };
142
+ if (auxLength) {
143
+ aux = this.parseAuxData(bytes, 16, auxLength);
144
+ }
145
+ const refCount = bytes.readInt32LE(16 + auxLength);
146
+ // read the indexes for each reference sequence
147
+ let firstDataLine;
148
+ let currOffset = 16 + auxLength + 4;
149
+ const indices = new Array(refCount).fill(0).map(() => {
150
+ // the binning index
151
+ const binCount = bytes.readInt32LE(currOffset);
152
+ currOffset += 4;
153
+ const binIndex = {};
154
+ let stats; // < provided by parsing a pseudo-bin, if present
155
+ for (let j = 0; j < binCount; j += 1) {
156
+ const bin = bytes.readUInt32LE(currOffset);
157
+ if (bin > this.maxBinNumber) {
158
+ // this is a fake bin that actually has stats information
159
+ // about the reference sequence in it
160
+ stats = this.parsePseudoBin(bytes, currOffset + 4);
161
+ currOffset += 4 + 8 + 4 + 16 + 16;
162
+ }
163
+ else {
164
+ const loffset = (0, virtualOffset_1.fromBytes)(bytes, currOffset + 4);
165
+ firstDataLine = this._findFirstData(firstDataLine, loffset);
166
+ const chunkCount = bytes.readInt32LE(currOffset + 12);
167
+ currOffset += 16;
168
+ const chunks = new Array(chunkCount);
169
+ for (let k = 0; k < chunkCount; k += 1) {
170
+ const u = (0, virtualOffset_1.fromBytes)(bytes, currOffset);
171
+ const v = (0, virtualOffset_1.fromBytes)(bytes, currOffset + 8);
172
+ currOffset += 16;
173
+ // this._findFirstData(data, u)
174
+ chunks[k] = new chunk_1.default(u, v, bin);
175
+ }
176
+ binIndex[bin] = chunks;
177
+ }
178
+ }
179
+ return { binIndex, stats };
180
+ });
181
+ return {
182
+ ...aux,
183
+ csi: true,
184
+ refCount,
185
+ maxBlockSize: 1 << 16,
186
+ firstDataLine,
187
+ csiVersion,
188
+ indices,
189
+ depth: this.depth,
190
+ maxBinNumber: this.maxBinNumber,
191
+ maxRefLength,
192
+ };
193
+ }
194
+ parsePseudoBin(bytes, offset) {
195
+ const lineCount = (0, util_1.longToNumber)(long_1.default.fromBytesLE(Array.prototype.slice.call(bytes, offset + 28, offset + 36), true));
196
+ return { lineCount };
197
+ }
198
+ async blocksForRange(refName, min, max, opts = {}) {
199
+ if (min < 0) {
200
+ min = 0;
201
+ }
202
+ const indexData = await this.parse(opts);
203
+ if (!indexData) {
204
+ return [];
205
+ }
206
+ const refId = indexData.refNameToId[refName];
207
+ const ba = indexData.indices[refId];
208
+ if (!ba) {
209
+ return [];
210
+ }
211
+ // const { linearIndex, binIndex } = indexes
212
+ const overlappingBins = this.reg2bins(min, max); // List of bin #s that overlap min, max
213
+ const chunks = [];
214
+ // Find chunks in overlapping bins. Leaf bins (< 4681) are not pruned
215
+ for (const [start, end] of overlappingBins) {
216
+ for (let bin = start; bin <= end; bin++) {
217
+ if (ba.binIndex[bin]) {
218
+ const binChunks = ba.binIndex[bin];
219
+ for (let c = 0; c < binChunks.length; ++c) {
220
+ chunks.push(new chunk_1.default(binChunks[c].minv, binChunks[c].maxv, bin));
221
+ }
222
+ }
223
+ }
224
+ }
225
+ return (0, util_1.optimizeChunks)(chunks, new virtualOffset_1.default(0, 0));
226
+ }
227
+ /**
228
+ * calculate the list of bins that may overlap with region [beg,end) (zero-based half-open)
229
+ */
230
+ reg2bins(beg, end) {
231
+ beg -= 1; // < convert to 1-based closed
232
+ if (beg < 1) {
233
+ beg = 1;
234
+ }
235
+ if (end > 2 ** 50) {
236
+ end = 2 ** 34;
237
+ } // 17 GiB ought to be enough for anybody
238
+ end -= 1;
239
+ let l = 0;
240
+ let t = 0;
241
+ let s = this.minShift + this.depth * 3;
242
+ const bins = [];
243
+ for (; l <= this.depth; s -= 3, t += lshift(1, l * 3), l += 1) {
244
+ const b = t + rshift(beg, s);
245
+ const e = t + rshift(end, s);
246
+ if (e - b + bins.length > this.maxBinNumber) {
247
+ throw new Error(`query ${beg}-${end} is too large for current binning scheme (shift ${this.minShift}, depth ${this.depth}), try a smaller query or a coarser index binning scheme`);
248
+ }
249
+ bins.push([b, e]);
250
+ }
251
+ return bins;
252
+ }
253
+ }
254
+ exports.default = CSI;
package/esm/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import TabixIndexedFile from './tabixIndexedFile';
2
+ import TBI from './tbi';
3
+ import CSI from './csi';
4
+ export { TabixIndexedFile, TBI, CSI };
package/esm/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CSI = exports.TBI = exports.TabixIndexedFile = void 0;
7
+ const tabixIndexedFile_1 = __importDefault(require("./tabixIndexedFile"));
8
+ exports.TabixIndexedFile = tabixIndexedFile_1.default;
9
+ const tbi_1 = __importDefault(require("./tbi"));
10
+ exports.TBI = tbi_1.default;
11
+ const csi_1 = __importDefault(require("./csi"));
12
+ exports.CSI = csi_1.default;
@@ -0,0 +1,32 @@
1
+ import { GenericFilehandle } from 'generic-filehandle';
2
+ import VirtualOffset from './virtualOffset';
3
+ import Chunk from './chunk';
4
+ export interface Options {
5
+ [key: string]: unknown;
6
+ signal?: AbortSignal;
7
+ }
8
+ export default abstract class IndexFile {
9
+ filehandle: GenericFilehandle;
10
+ renameRefSeq: (arg0: string) => string;
11
+ private _parseCache;
12
+ /**
13
+ * @param {filehandle} filehandle
14
+ * @param {function} [renameRefSeqs]
15
+ */
16
+ constructor({ filehandle, renameRefSeqs, }: {
17
+ filehandle: GenericFilehandle;
18
+ renameRefSeqs?: (a: string) => string;
19
+ });
20
+ abstract lineCount(refName: string, args: Options): Promise<number>;
21
+ protected abstract _parse(opts: Options): Promise<{
22
+ refNameToId: {
23
+ [key: string]: number;
24
+ };
25
+ refIdToName: string[];
26
+ }>;
27
+ getMetadata(opts?: Options): Promise<any>;
28
+ abstract blocksForRange(refName: string, start: number, end: number, opts: Options): Promise<Chunk[]>;
29
+ _findFirstData(currentFdl: VirtualOffset | undefined, virtualOffset: VirtualOffset): VirtualOffset;
30
+ parse(opts?: Options): Promise<any>;
31
+ hasRefSeq(seqId: number, opts?: Options): Promise<boolean>;
32
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const abortable_promise_cache_1 = __importDefault(require("abortable-promise-cache"));
7
+ const quick_lru_1 = __importDefault(require("quick-lru"));
8
+ class IndexFile {
9
+ /**
10
+ * @param {filehandle} filehandle
11
+ * @param {function} [renameRefSeqs]
12
+ */
13
+ constructor({ filehandle, renameRefSeqs = (n) => n, }) {
14
+ this.filehandle = filehandle;
15
+ this.renameRefSeq = renameRefSeqs;
16
+ }
17
+ async getMetadata(opts = {}) {
18
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
+ const { indices, ...rest } = await this.parse(opts);
20
+ return rest;
21
+ }
22
+ _findFirstData(currentFdl, virtualOffset) {
23
+ if (currentFdl) {
24
+ return currentFdl.compareTo(virtualOffset) > 0
25
+ ? virtualOffset
26
+ : currentFdl;
27
+ }
28
+ else {
29
+ return virtualOffset;
30
+ }
31
+ }
32
+ async parse(opts = {}) {
33
+ if (!this._parseCache) {
34
+ this._parseCache = new abortable_promise_cache_1.default({
35
+ cache: new quick_lru_1.default({ maxSize: 1 }),
36
+ fill: () => this._parse(opts),
37
+ });
38
+ }
39
+ return this._parseCache.get('index', null, undefined);
40
+ }
41
+ async hasRefSeq(seqId, opts = {}) {
42
+ return !!((await this.parse(opts)).indices[seqId] || {}).binIndex;
43
+ }
44
+ }
45
+ exports.default = IndexFile;
@@ -0,0 +1,118 @@
1
+ /// <reference types="node" />
2
+ import { GenericFilehandle } from 'generic-filehandle';
3
+ import { Options } from './indexFile';
4
+ import Chunk from './chunk';
5
+ declare type GetLinesCallback = (line: string, fileOffset: number) => Promise<void>;
6
+ interface GetLinesOpts {
7
+ [key: string]: unknown;
8
+ signal?: AbortSignal;
9
+ lineCallback: GetLinesCallback;
10
+ }
11
+ export default class TabixIndexedFile {
12
+ private filehandle;
13
+ private index;
14
+ private chunkSizeLimit;
15
+ private renameRefSeq;
16
+ private chunkCache;
17
+ /**
18
+ * @param {object} args
19
+ * @param {string} [args.path]
20
+ * @param {filehandle} [args.filehandle]
21
+ * @param {string} [args.tbiPath]
22
+ * @param {filehandle} [args.tbiFilehandle]
23
+ * @param {string} [args.csiPath]
24
+ * @param {filehandle} [args.csiFilehandle]
25
+ * @param {chunkSizeLimit} default 50MiB
26
+ * @param {function} [args.renameRefSeqs] optional function with sig `string => string` to transform
27
+ * reference sequence names for the purpose of indexing and querying. note that the data that is returned is
28
+ * not altered, just the names of the reference sequences that are used for querying.
29
+ * @param {number} [args.chunkCacheSize] maximum size in bytes of the chunk cache. default 5MB
30
+ * @param {number} [args.blockCacheSize] maximum size in bytes of the block cache. default 5MB
31
+ */
32
+ constructor({ path, filehandle, tbiPath, tbiFilehandle, csiPath, csiFilehandle, chunkSizeLimit, renameRefSeqs, chunkCacheSize, }: {
33
+ path?: string;
34
+ filehandle?: GenericFilehandle;
35
+ tbiPath?: string;
36
+ tbiFilehandle?: GenericFilehandle;
37
+ csiPath?: string;
38
+ csiFilehandle?: GenericFilehandle;
39
+ chunkSizeLimit?: number;
40
+ renameRefSeqs?: (n: string) => string;
41
+ chunkCacheSize?: number;
42
+ });
43
+ /**
44
+ * @param {string} refName name of the reference sequence
45
+ * @param {number} start start of the region (in 0-based half-open coordinates)
46
+ * @param {number} end end of the region (in 0-based half-open coordinates)
47
+ * @param {function|object} lineCallback callback called for each line in the region. can also pass a object param containing obj.lineCallback, obj.signal, etc
48
+ * @returns {Promise} resolved when the whole read is finished, rejected on error
49
+ */
50
+ getLines(refName: string, start: number, end: number, opts: GetLinesOpts | GetLinesCallback): Promise<void>;
51
+ getMetadata(opts?: Options): Promise<any>;
52
+ /**
53
+ * get a buffer containing the "header" region of
54
+ * the file, which are the bytes up to the first
55
+ * non-meta line
56
+ *
57
+ * @returns {Promise} for a buffer
58
+ */
59
+ getHeaderBuffer(opts?: Options): Promise<Buffer>;
60
+ /**
61
+ * get a string containing the "header" region of the
62
+ * file, is the portion up to the first non-meta line
63
+ *
64
+ * @returns {Promise} for a string
65
+ */
66
+ getHeader(opts?: Options): Promise<string>;
67
+ /**
68
+ * get an array of reference sequence names, in the order in which
69
+ * they occur in the file.
70
+ *
71
+ * reference sequence renaming is not applied to these names.
72
+ *
73
+ * @returns {Promise} for an array of string sequence names
74
+ */
75
+ getReferenceSequenceNames(opts?: Options): Promise<any>;
76
+ /**
77
+ * @param {object} metadata metadata object from the parsed index,
78
+ * containing columnNumbers, metaChar, and format
79
+ * @param {string} regionRefName
80
+ * @param {number} regionStart region start coordinate (0-based-half-open)
81
+ * @param {number} regionEnd region end coordinate (0-based-half-open)
82
+ * @param {array[string]} line
83
+ * @returns {object} like `{startCoordinate, overlaps}`. overlaps is boolean,
84
+ * true if line is a data line that overlaps the given region
85
+ */
86
+ checkLine({ columnNumbers, metaChar, coordinateType, format, }: {
87
+ columnNumbers: {
88
+ ref: number;
89
+ start: number;
90
+ end: number;
91
+ };
92
+ metaChar: string;
93
+ coordinateType: string;
94
+ format: string;
95
+ }, regionRefName: string, regionStart: number, regionEnd: number, line: string): {
96
+ overlaps: boolean;
97
+ startCoordinate?: undefined;
98
+ } | {
99
+ startCoordinate: number;
100
+ overlaps: boolean;
101
+ };
102
+ _getVcfEnd(startCoordinate: number, refSeq: string, info: any): number;
103
+ /**
104
+ * return the approximate number of data lines in the given reference sequence
105
+ * @param {string} refSeq reference sequence name
106
+ * @returns {Promise} for number of data lines present on that reference sequence
107
+ */
108
+ lineCount(refName: string, opts?: Options): Promise<number>;
109
+ _readRegion(position: number, compressedSize: number, opts?: Options): Promise<Buffer>;
110
+ /**
111
+ * read and uncompress the data in a chunk (composed of one or more
112
+ * contiguous bgzip blocks) of the file
113
+ * @param {Chunk} chunk
114
+ * @returns {Promise} for a string chunk of the file
115
+ */
116
+ readChunk(chunk: Chunk, opts?: Options): Promise<any>;
117
+ }
118
+ export {};