@gmod/tabix 1.5.6 → 1.5.7
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/CHANGELOG.md +24 -21
- package/dist/csi.d.ts +40 -12
- package/dist/csi.js +14 -18
- package/dist/csi.js.map +1 -1
- package/dist/indexFile.d.ts +28 -8
- package/dist/indexFile.js +5 -14
- package/dist/indexFile.js.map +1 -1
- package/dist/tabixIndexedFile.d.ts +37 -35
- package/dist/tabixIndexedFile.js +29 -42
- package/dist/tabixIndexedFile.js.map +1 -1
- package/dist/tbi.d.ts +4 -4
- package/dist/tbi.js +4 -4
- package/dist/tbi.js.map +1 -1
- package/esm/csi.d.ts +40 -12
- package/esm/csi.js +14 -18
- package/esm/csi.js.map +1 -1
- package/esm/indexFile.d.ts +28 -8
- package/esm/indexFile.js +5 -11
- package/esm/indexFile.js.map +1 -1
- package/esm/tabixIndexedFile.d.ts +37 -35
- package/esm/tabixIndexedFile.js +29 -42
- package/esm/tabixIndexedFile.js.map +1 -1
- package/esm/tbi.d.ts +4 -4
- package/esm/tbi.js +4 -4
- package/esm/tbi.js.map +1 -1
- package/package.json +14 -14
- package/src/csi.ts +16 -28
- package/src/indexFile.ts +17 -16
- package/src/tabixIndexedFile.ts +45 -64
- package/src/tbi.ts +4 -4
- package/src/declare.d.ts +0 -2
package/src/tabixIndexedFile.ts
CHANGED
|
@@ -3,7 +3,7 @@ import LRU from 'quick-lru'
|
|
|
3
3
|
import { GenericFilehandle, LocalFile } from 'generic-filehandle'
|
|
4
4
|
import { unzip, unzipChunkSlice } from '@gmod/bgzf-filehandle'
|
|
5
5
|
import { checkAbortSignal } from './util'
|
|
6
|
-
import IndexFile, { Options } from './indexFile'
|
|
6
|
+
import IndexFile, { Options, IndexData } from './indexFile'
|
|
7
7
|
|
|
8
8
|
import Chunk from './chunk'
|
|
9
9
|
import TBI from './tbi'
|
|
@@ -20,17 +20,23 @@ interface GetLinesOpts {
|
|
|
20
20
|
lineCallback: GetLinesCallback
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
interface ReadChunk {
|
|
24
|
+
buffer: Buffer
|
|
25
|
+
cpositions: number[]
|
|
26
|
+
dpositions: number[]
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
function timeout(time: number) {
|
|
24
|
-
return new Promise(resolve =>
|
|
25
|
-
setTimeout(resolve, time)
|
|
26
|
-
})
|
|
30
|
+
return new Promise(resolve => setTimeout(resolve, time))
|
|
27
31
|
}
|
|
28
32
|
export default class TabixIndexedFile {
|
|
29
33
|
private filehandle: GenericFilehandle
|
|
30
34
|
private index: IndexFile
|
|
31
35
|
private chunkSizeLimit: number
|
|
36
|
+
private yieldTime: number
|
|
32
37
|
private renameRefSeq: (n: string) => string
|
|
33
|
-
private chunkCache:
|
|
38
|
+
private chunkCache: AbortablePromiseCache<Chunk, ReadChunk>
|
|
39
|
+
|
|
34
40
|
/**
|
|
35
41
|
* @param {object} args
|
|
36
42
|
* @param {string} [args.path]
|
|
@@ -39,12 +45,10 @@ export default class TabixIndexedFile {
|
|
|
39
45
|
* @param {filehandle} [args.tbiFilehandle]
|
|
40
46
|
* @param {string} [args.csiPath]
|
|
41
47
|
* @param {filehandle} [args.csiFilehandle]
|
|
42
|
-
* @param {
|
|
48
|
+
* @param {number} [args.yieldTime] yield to main thread after N milliseconds if reading features is taking a long time to avoid hanging main thread
|
|
43
49
|
* @param {function} [args.renameRefSeqs] optional function with sig `string => string` to transform
|
|
44
50
|
* reference sequence names for the purpose of indexing and querying. note that the data that is returned is
|
|
45
51
|
* not altered, just the names of the reference sequences that are used for querying.
|
|
46
|
-
* @param {number} [args.chunkCacheSize] maximum size in bytes of the chunk cache. default 5MB
|
|
47
|
-
* @param {number} [args.blockCacheSize] maximum size in bytes of the block cache. default 5MB
|
|
48
52
|
*/
|
|
49
53
|
constructor({
|
|
50
54
|
path,
|
|
@@ -53,6 +57,7 @@ export default class TabixIndexedFile {
|
|
|
53
57
|
tbiFilehandle,
|
|
54
58
|
csiPath,
|
|
55
59
|
csiFilehandle,
|
|
60
|
+
yieldTime = 500,
|
|
56
61
|
chunkSizeLimit = 50000000,
|
|
57
62
|
renameRefSeqs = n => n,
|
|
58
63
|
chunkCacheSize = 5 * 2 ** 20,
|
|
@@ -63,6 +68,7 @@ export default class TabixIndexedFile {
|
|
|
63
68
|
tbiFilehandle?: GenericFilehandle
|
|
64
69
|
csiPath?: string
|
|
65
70
|
csiFilehandle?: GenericFilehandle
|
|
71
|
+
yieldTime: number
|
|
66
72
|
chunkSizeLimit?: number
|
|
67
73
|
renameRefSeqs?: (n: string) => string
|
|
68
74
|
chunkCacheSize?: number
|
|
@@ -108,21 +114,20 @@ export default class TabixIndexedFile {
|
|
|
108
114
|
|
|
109
115
|
this.chunkSizeLimit = chunkSizeLimit
|
|
110
116
|
this.renameRefSeq = renameRefSeqs
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
fill: this.readChunk.bind(this),
|
|
117
|
+
this.yieldTime = yieldTime
|
|
118
|
+
this.chunkCache = new AbortablePromiseCache<Chunk, ReadChunk>({
|
|
119
|
+
cache: new LRU({ maxSize: Math.floor(chunkCacheSize / (1 << 16)) }),
|
|
120
|
+
fill: (args: Chunk, signal?: AbortSignal) =>
|
|
121
|
+
this.readChunk(args, { signal }),
|
|
117
122
|
})
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
/**
|
|
121
|
-
* @param
|
|
122
|
-
* @param
|
|
123
|
-
* @param
|
|
124
|
-
* @param
|
|
125
|
-
* @returns
|
|
126
|
+
* @param refName name of the reference sequence
|
|
127
|
+
* @param start start of the region (in 0-based half-open coordinates)
|
|
128
|
+
* @param end end of the region (in 0-based half-open coordinates)
|
|
129
|
+
* @param opts callback called for each line in the region. can also pass a object param containing obj.lineCallback, obj.signal, etc
|
|
130
|
+
* @returns promise that is resolved when the whole read is finished, rejected on error
|
|
126
131
|
*/
|
|
127
132
|
async getLines(
|
|
128
133
|
refName: string,
|
|
@@ -188,7 +193,6 @@ export default class TabixIndexedFile {
|
|
|
188
193
|
const { buffer, cpositions, dpositions } = await this.chunkCache.get(
|
|
189
194
|
c.toString(),
|
|
190
195
|
c,
|
|
191
|
-
signal,
|
|
192
196
|
)
|
|
193
197
|
|
|
194
198
|
checkAbortSignal(signal)
|
|
@@ -253,7 +257,7 @@ export default class TabixIndexedFile {
|
|
|
253
257
|
}
|
|
254
258
|
|
|
255
259
|
// yield if we have emitted beyond the yield limit
|
|
256
|
-
if (last - Date.now() >
|
|
260
|
+
if (this.yieldTime && last - Date.now() > this.yieldTime) {
|
|
257
261
|
last = Date.now()
|
|
258
262
|
checkAbortSignal(signal)
|
|
259
263
|
await timeout(1)
|
|
@@ -271,18 +275,13 @@ export default class TabixIndexedFile {
|
|
|
271
275
|
* get a buffer containing the "header" region of
|
|
272
276
|
* the file, which are the bytes up to the first
|
|
273
277
|
* non-meta line
|
|
274
|
-
*
|
|
275
|
-
* @returns {Promise} for a buffer
|
|
276
278
|
*/
|
|
277
279
|
async getHeaderBuffer(opts: Options = {}) {
|
|
278
280
|
const { firstDataLine, metaChar, maxBlockSize } = await this.getMetadata(
|
|
279
281
|
opts,
|
|
280
282
|
)
|
|
281
283
|
checkAbortSignal(opts.signal)
|
|
282
|
-
const maxFetch =
|
|
283
|
-
firstDataLine && firstDataLine.blockPosition
|
|
284
|
-
? firstDataLine.blockPosition + maxBlockSize
|
|
285
|
-
: maxBlockSize
|
|
284
|
+
const maxFetch = (firstDataLine?.blockPosition || 0) + maxBlockSize
|
|
286
285
|
// TODO: what if we don't have a firstDataLine, and the header
|
|
287
286
|
// actually takes up more than one block? this case is not covered here
|
|
288
287
|
|
|
@@ -312,7 +311,7 @@ export default class TabixIndexedFile {
|
|
|
312
311
|
lastNewline = i
|
|
313
312
|
}
|
|
314
313
|
}
|
|
315
|
-
bytes = bytes.
|
|
314
|
+
bytes = bytes.subarray(0, lastNewline + 1)
|
|
316
315
|
}
|
|
317
316
|
return bytes
|
|
318
317
|
}
|
|
@@ -325,17 +324,13 @@ export default class TabixIndexedFile {
|
|
|
325
324
|
*/
|
|
326
325
|
async getHeader(opts: Options = {}) {
|
|
327
326
|
const bytes = await this.getHeaderBuffer(opts)
|
|
328
|
-
checkAbortSignal(opts.signal)
|
|
329
327
|
return bytes.toString('utf8')
|
|
330
328
|
}
|
|
331
329
|
|
|
332
330
|
/**
|
|
333
331
|
* get an array of reference sequence names, in the order in which
|
|
334
|
-
* they occur in the file.
|
|
335
|
-
*
|
|
336
|
-
* reference sequence renaming is not applied to these names.
|
|
337
|
-
*
|
|
338
|
-
* @returns {Promise} for an array of string sequence names
|
|
332
|
+
* they occur in the file. reference sequence renaming is not applied
|
|
333
|
+
* to these names.
|
|
339
334
|
*/
|
|
340
335
|
async getReferenceSequenceNames(opts: Options = {}) {
|
|
341
336
|
const metadata = await this.getMetadata(opts)
|
|
@@ -353,22 +348,13 @@ export default class TabixIndexedFile {
|
|
|
353
348
|
* true if line is a data line that overlaps the given region
|
|
354
349
|
*/
|
|
355
350
|
checkLine(
|
|
356
|
-
|
|
357
|
-
columnNumbers,
|
|
358
|
-
metaChar,
|
|
359
|
-
coordinateType,
|
|
360
|
-
format,
|
|
361
|
-
}: {
|
|
362
|
-
columnNumbers: { ref: number; start: number; end: number }
|
|
363
|
-
metaChar: string
|
|
364
|
-
coordinateType: string
|
|
365
|
-
format: string
|
|
366
|
-
},
|
|
351
|
+
metadata: IndexData,
|
|
367
352
|
regionRefName: string,
|
|
368
353
|
regionStart: number,
|
|
369
354
|
regionEnd: number,
|
|
370
355
|
line: string,
|
|
371
356
|
) {
|
|
357
|
+
const { columnNumbers, metaChar, coordinateType, format } = metadata
|
|
372
358
|
// skip meta lines
|
|
373
359
|
if (line.charAt(0) === metaChar) {
|
|
374
360
|
return { overlaps: false }
|
|
@@ -479,48 +465,43 @@ export default class TabixIndexedFile {
|
|
|
479
465
|
|
|
480
466
|
/**
|
|
481
467
|
* return the approximate number of data lines in the given reference sequence
|
|
482
|
-
* @param
|
|
483
|
-
* @returns
|
|
468
|
+
* @param refSeq reference sequence name
|
|
469
|
+
* @returns number of data lines present on that reference sequence
|
|
484
470
|
*/
|
|
485
471
|
async lineCount(refName: string, opts: Options = {}) {
|
|
486
472
|
return this.index.lineCount(refName, opts)
|
|
487
473
|
}
|
|
488
474
|
|
|
489
|
-
async _readRegion(
|
|
490
|
-
|
|
491
|
-
compressedSize: number,
|
|
492
|
-
opts: Options = {},
|
|
493
|
-
) {
|
|
475
|
+
async _readRegion(pos: number, size: number, opts: Options = {}) {
|
|
476
|
+
const b = Buffer.alloc(size)
|
|
494
477
|
const { bytesRead, buffer } = await this.filehandle.read(
|
|
495
|
-
|
|
478
|
+
b,
|
|
496
479
|
0,
|
|
497
|
-
|
|
498
|
-
|
|
480
|
+
size,
|
|
481
|
+
pos,
|
|
499
482
|
opts,
|
|
500
483
|
)
|
|
501
484
|
|
|
502
|
-
return
|
|
485
|
+
return buffer.subarray(0, bytesRead)
|
|
503
486
|
}
|
|
504
487
|
|
|
505
488
|
/**
|
|
506
489
|
* read and uncompress the data in a chunk (composed of one or more
|
|
507
490
|
* contiguous bgzip blocks) of the file
|
|
508
|
-
* @param {Chunk} chunk
|
|
509
|
-
* @returns {Promise} for a string chunk of the file
|
|
510
491
|
*/
|
|
511
|
-
async readChunk(
|
|
492
|
+
async readChunk(c: Chunk, opts: Options = {}) {
|
|
512
493
|
// fetch the uncompressed data, uncompress carefully a block at a time,
|
|
513
494
|
// and stop when done
|
|
514
495
|
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
496
|
+
const data = await this._readRegion(
|
|
497
|
+
c.minv.blockPosition,
|
|
498
|
+
c.fetchedSize(),
|
|
518
499
|
opts,
|
|
519
500
|
)
|
|
520
501
|
try {
|
|
521
|
-
return unzipChunkSlice(
|
|
502
|
+
return unzipChunkSlice(data, c)
|
|
522
503
|
} catch (e) {
|
|
523
|
-
throw new Error(`error decompressing
|
|
504
|
+
throw new Error(`error decompressing c ${c.toString()} ${e}`)
|
|
524
505
|
}
|
|
525
506
|
}
|
|
526
507
|
}
|
package/src/tbi.ts
CHANGED
|
@@ -42,10 +42,10 @@ export default class TabixIndex extends IndexFile {
|
|
|
42
42
|
return -1
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
// memoize
|
|
46
45
|
// fetch and parse the index
|
|
47
46
|
async _parse(opts: Options = {}) {
|
|
48
|
-
const
|
|
47
|
+
const buf = await this.filehandle.readFile(opts)
|
|
48
|
+
const bytes = await unzip(buf)
|
|
49
49
|
checkAbortSignal(opts.signal)
|
|
50
50
|
|
|
51
51
|
// check TBI magic numbers
|
|
@@ -83,7 +83,7 @@ export default class TabixIndex extends IndexFile {
|
|
|
83
83
|
// read sequence dictionary
|
|
84
84
|
const nameSectionLength = bytes.readInt32LE(32)
|
|
85
85
|
const { refNameToId, refIdToName } = this._parseNameBytes(
|
|
86
|
-
bytes.
|
|
86
|
+
bytes.subarray(36, 36 + nameSectionLength),
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
// read the indexes for each reference sequence
|
|
@@ -155,7 +155,7 @@ export default class TabixIndex extends IndexFile {
|
|
|
155
155
|
parsePseudoBin(bytes: Buffer, offset: number) {
|
|
156
156
|
const lineCount = longToNumber(
|
|
157
157
|
Long.fromBytesLE(
|
|
158
|
-
bytes.
|
|
158
|
+
bytes.subarray(offset + 16, offset + 24) as unknown as number[],
|
|
159
159
|
true,
|
|
160
160
|
),
|
|
161
161
|
)
|
package/src/declare.d.ts
DELETED