@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.
@@ -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: any
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 {chunkSizeLimit} default 50MiB
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.chunkCache = new AbortablePromiseCache({
112
- cache: new LRU({
113
- maxSize: Math.floor(chunkCacheSize / (1 << 16)),
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 {string} refName name of the reference sequence
122
- * @param {number} start start of the region (in 0-based half-open coordinates)
123
- * @param {number} end end of the region (in 0-based half-open coordinates)
124
- * @param {function|object} lineCallback callback called for each line in the region. can also pass a object param containing obj.lineCallback, obj.signal, etc
125
- * @returns {Promise} resolved when the whole read is finished, rejected on error
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() > 500) {
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.slice(0, lastNewline + 1)
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 {string} refSeq reference sequence name
483
- * @returns {Promise} for number of data lines present on that reference sequence
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
- position: number,
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
- Buffer.alloc(compressedSize),
478
+ b,
496
479
  0,
497
- compressedSize,
498
- position,
480
+ size,
481
+ pos,
499
482
  opts,
500
483
  )
501
484
 
502
- return bytesRead < compressedSize ? buffer.slice(0, bytesRead) : buffer
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(chunk: Chunk, opts: Options = {}) {
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 compressedData = await this._readRegion(
516
- chunk.minv.blockPosition,
517
- chunk.fetchedSize(),
496
+ const data = await this._readRegion(
497
+ c.minv.blockPosition,
498
+ c.fetchedSize(),
518
499
  opts,
519
500
  )
520
501
  try {
521
- return unzipChunkSlice(compressedData, chunk)
502
+ return unzipChunkSlice(data, c)
522
503
  } catch (e) {
523
- throw new Error(`error decompressing chunk ${chunk.toString()} ${e}`)
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 bytes = await unzip((await this.filehandle.readFile(opts)) as Buffer)
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.slice(36, 36 + nameSectionLength),
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.slice(offset + 16, offset + 24) as unknown as number[],
158
+ bytes.subarray(offset + 16, offset + 24) as unknown as number[],
159
159
  true,
160
160
  ),
161
161
  )
package/src/declare.d.ts DELETED
@@ -1,2 +0,0 @@
1
- declare module 'abortable-promise-cache'
2
- declare module '@gmod/bgzf-filehandle'