@gmod/tabix 1.5.14 → 1.6.0

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +9 -5
  2. package/README.md +182 -35
  3. package/dist/chunk.js +0 -1
  4. package/dist/chunk.js.map +1 -1
  5. package/dist/csi.d.ts +1 -1
  6. package/dist/csi.js +132 -128
  7. package/dist/csi.js.map +1 -1
  8. package/dist/indexFile.js +15 -41
  9. package/dist/indexFile.js.map +1 -1
  10. package/dist/tabix-bundle.js +2 -0
  11. package/dist/tabix-bundle.js.LICENSE.txt +8 -0
  12. package/dist/tabixIndexedFile.d.ts +49 -25
  13. package/dist/tabixIndexedFile.js +214 -195
  14. package/dist/tabixIndexedFile.js.map +1 -1
  15. package/dist/tbi.js +169 -173
  16. package/dist/tbi.js.map +1 -1
  17. package/dist/util.d.ts +7 -6
  18. package/dist/util.js +9 -19
  19. package/dist/util.js.map +1 -1
  20. package/dist/virtualOffset.d.ts +0 -1
  21. package/dist/virtualOffset.js +0 -13
  22. package/dist/virtualOffset.js.map +1 -1
  23. package/esm/chunk.js +0 -1
  24. package/esm/chunk.js.map +1 -1
  25. package/esm/csi.d.ts +1 -1
  26. package/esm/csi.js +4 -4
  27. package/esm/csi.js.map +1 -1
  28. package/esm/indexFile.js +1 -1
  29. package/esm/indexFile.js.map +1 -1
  30. package/esm/tabixIndexedFile.d.ts +49 -25
  31. package/esm/tabixIndexedFile.js +134 -90
  32. package/esm/tabixIndexedFile.js.map +1 -1
  33. package/esm/tbi.js +6 -5
  34. package/esm/tbi.js.map +1 -1
  35. package/esm/util.d.ts +7 -6
  36. package/esm/util.js +6 -5
  37. package/esm/util.js.map +1 -1
  38. package/esm/virtualOffset.d.ts +0 -1
  39. package/esm/virtualOffset.js +0 -13
  40. package/esm/virtualOffset.js.map +1 -1
  41. package/package.json +24 -19
  42. package/src/chunk.ts +0 -1
  43. package/src/csi.ts +6 -5
  44. package/src/indexFile.ts +1 -1
  45. package/src/tabixIndexedFile.ts +141 -106
  46. package/src/tbi.ts +7 -6
  47. package/src/util.ts +7 -6
  48. package/src/virtualOffset.ts +7 -21
@@ -1,7 +1,7 @@
1
1
  import AbortablePromiseCache from '@gmod/abortable-promise-cache'
2
2
  import LRU from 'quick-lru'
3
3
  import { Buffer } from 'buffer'
4
- import { GenericFilehandle, LocalFile } from 'generic-filehandle'
4
+ import { GenericFilehandle, RemoteFile, LocalFile } from 'generic-filehandle'
5
5
  import { unzip, unzipChunkSlice } from '@gmod/bgzf-filehandle'
6
6
  import { checkAbortSignal } from './util'
7
7
  import IndexFile, { Options, IndexData } from './indexFile'
@@ -10,6 +10,11 @@ import Chunk from './chunk'
10
10
  import TBI from './tbi'
11
11
  import CSI from './csi'
12
12
 
13
+ function isASCII(str: string) {
14
+ // eslint-disable-next-line no-control-regex
15
+ return /^[\u0000-\u007F]*$/.test(str)
16
+ }
17
+
13
18
  type GetLinesCallback = (line: string, fileOffset: number) => void
14
19
 
15
20
  const decoder =
@@ -27,50 +32,60 @@ interface ReadChunk {
27
32
  dpositions: number[]
28
33
  }
29
34
 
30
- function timeout(time: number) {
31
- return new Promise(resolve => setTimeout(resolve, time))
32
- }
33
35
  export default class TabixIndexedFile {
34
36
  private filehandle: GenericFilehandle
35
37
  private index: IndexFile
36
- private chunkSizeLimit: number
37
- private yieldTime: number
38
38
  private renameRefSeq: (n: string) => string
39
39
  private chunkCache: AbortablePromiseCache<Chunk, ReadChunk>
40
40
 
41
41
  /**
42
42
  * @param {object} args
43
+ *
43
44
  * @param {string} [args.path]
45
+ *
44
46
  * @param {filehandle} [args.filehandle]
47
+ *
45
48
  * @param {string} [args.tbiPath]
49
+ *
46
50
  * @param {filehandle} [args.tbiFilehandle]
51
+ *
47
52
  * @param {string} [args.csiPath]
53
+ *
48
54
  * @param {filehandle} [args.csiFilehandle]
49
- * @param {number} [args.yieldTime] yield to main thread after N milliseconds if reading features is taking a long time to avoid hanging main thread
50
- * @param {function} [args.renameRefSeqs] optional function with sig `string => string` to transform
51
- * reference sequence names for the purpose of indexing and querying. note that the data that is returned is
52
- * not altered, just the names of the reference sequences that are used for querying.
55
+ *
56
+ * @param {url} [args.url]
57
+ *
58
+ * @param {csiUrl} [args.csiUrl]
59
+ *
60
+ * @param {tbiUrl} [args.tbiUrl]
61
+ *
62
+ * @param {function} [args.renameRefSeqs] optional function with sig `string
63
+ * => string` to transform reference sequence names for the purpose of
64
+ * indexing and querying. note that the data that is returned is not altered,
65
+ * just the names of the reference sequences that are used for querying.
53
66
  */
54
67
  constructor({
55
68
  path,
56
69
  filehandle,
70
+ url,
57
71
  tbiPath,
72
+ tbiUrl,
58
73
  tbiFilehandle,
59
74
  csiPath,
75
+ csiUrl,
60
76
  csiFilehandle,
61
- yieldTime = 500,
62
- chunkSizeLimit = 50000000,
63
77
  renameRefSeqs = n => n,
64
78
  chunkCacheSize = 5 * 2 ** 20,
65
79
  }: {
66
80
  path?: string
67
81
  filehandle?: GenericFilehandle
82
+ url?: string
68
83
  tbiPath?: string
84
+ tbiUrl?: string
69
85
  tbiFilehandle?: GenericFilehandle
70
86
  csiPath?: string
87
+ csiUrl?: string
71
88
  csiFilehandle?: GenericFilehandle
72
- yieldTime?: number
73
- chunkSizeLimit?: number
74
89
  renameRefSeqs?: (n: string) => string
75
90
  chunkCacheSize?: number
76
91
  }) {
@@ -78,6 +93,8 @@ export default class TabixIndexedFile {
78
93
  this.filehandle = filehandle
79
94
  } else if (path) {
80
95
  this.filehandle = new LocalFile(path)
96
+ } else if (url) {
97
+ this.filehandle = new RemoteFile(url)
81
98
  } else {
82
99
  throw new TypeError('must provide either filehandle or path')
83
100
  }
@@ -107,15 +124,25 @@ export default class TabixIndexedFile {
107
124
  filehandle: new LocalFile(`${path}.tbi`),
108
125
  renameRefSeqs,
109
126
  })
127
+ } else if (csiUrl) {
128
+ this.index = new CSI({
129
+ filehandle: new RemoteFile(csiUrl),
130
+ })
131
+ } else if (tbiUrl) {
132
+ this.index = new TBI({
133
+ filehandle: new RemoteFile(tbiUrl),
134
+ })
135
+ } else if (url) {
136
+ this.index = new TBI({
137
+ filehandle: new RemoteFile(`${url}.tbi`),
138
+ })
110
139
  } else {
111
140
  throw new TypeError(
112
- 'must provide one of tbiFilehandle, tbiPath, csiFilehandle, or csiPath',
141
+ 'must provide one of tbiFilehandle, tbiPath, csiFilehandle, csiPath, tbiUrl, csiUrl',
113
142
  )
114
143
  }
115
144
 
116
- this.chunkSizeLimit = chunkSizeLimit
117
145
  this.renameRefSeq = renameRefSeqs
118
- this.yieldTime = yieldTime
119
146
  this.chunkCache = new AbortablePromiseCache<Chunk, ReadChunk>({
120
147
  cache: new LRU({ maxSize: Math.floor(chunkCacheSize / (1 << 16)) }),
121
148
  fill: (args: Chunk, signal?: AbortSignal) =>
@@ -125,10 +152,16 @@ export default class TabixIndexedFile {
125
152
 
126
153
  /**
127
154
  * @param refName name of the reference sequence
155
+ *
128
156
  * @param start start of the region (in 0-based half-open coordinates)
157
+ *
129
158
  * @param end end of the region (in 0-based half-open coordinates)
130
- * @param opts callback called for each line in the region. can also pass a object param containing obj.lineCallback, obj.signal, etc
131
- * @returns promise that is resolved when the whole read is finished, rejected on error
159
+ *
160
+ * @param opts callback called for each line in the region. can also pass a
161
+ * object param containing obj.lineCallback, obj.signal, etc
162
+ *
163
+ * @returns promise that is resolved when the whole read is finished,
164
+ * rejected on error
132
165
  */
133
166
  async getLines(
134
167
  refName: string,
@@ -139,9 +172,7 @@ export default class TabixIndexedFile {
139
172
  let signal: AbortSignal | undefined
140
173
  let options: Options = {}
141
174
  let callback: (line: string, lineOffset: number) => void
142
- if (opts === undefined) {
143
- throw new TypeError('line callback must be provided')
144
- }
175
+
145
176
  if (typeof opts === 'function') {
146
177
  callback = opts
147
178
  } else {
@@ -149,12 +180,6 @@ export default class TabixIndexedFile {
149
180
  callback = opts.lineCallback
150
181
  signal = opts.signal
151
182
  }
152
- if (refName === undefined) {
153
- throw new TypeError('must provide a reference sequence name')
154
- }
155
- if (!callback) {
156
- throw new TypeError('line callback must be provided')
157
- }
158
183
 
159
184
  const metadata = await this.index.getMetadata(options)
160
185
  checkAbortSignal(signal)
@@ -172,21 +197,8 @@ export default class TabixIndexedFile {
172
197
  const chunks = await this.index.blocksForRange(refName, start, end, options)
173
198
  checkAbortSignal(signal)
174
199
 
175
- // check the chunks for any that are over the size limit. if
176
- // any are, don't fetch any of them
177
- for (const chunk of chunks) {
178
- const size = chunk.fetchedSize()
179
- if (size > this.chunkSizeLimit) {
180
- throw new Error(
181
- `Too much data. Chunk size ${size.toLocaleString()} bytes exceeds chunkSizeLimit of ${this.chunkSizeLimit.toLocaleString()}.`,
182
- )
183
- }
184
- }
185
-
186
200
  // now go through each chunk and parse and filter the lines out of it
187
- let last = Date.now()
188
201
  for (const c of chunks) {
189
- let previousStartCoordinate: number | undefined
190
202
  const { buffer, cpositions, dpositions } = await this.chunkCache.get(
191
203
  c.toString(),
192
204
  c,
@@ -196,16 +208,33 @@ export default class TabixIndexedFile {
196
208
  checkAbortSignal(signal)
197
209
  let blockStart = 0
198
210
  let pos = 0
199
- while (blockStart < buffer.length) {
200
- const n = buffer.indexOf('\n', blockStart)
201
- if (n === -1) {
202
- break
211
+
212
+ const str = decoder?.decode(buffer) ?? buffer.toString()
213
+ // fast path, Buffer is just ASCII chars and not gigantor, can be
214
+ // converted to string and processed directly. if it is not ASCII or
215
+ // gigantic (chrome max str len is 512Mb), we have to decode line by line
216
+ const strIsASCII = buffer.length < 500_000_000 && isASCII(str)
217
+ while (blockStart < str.length) {
218
+ let line: string
219
+ let n: number
220
+ if (strIsASCII) {
221
+ n = str.indexOf('\n', blockStart)
222
+ if (n === -1) {
223
+ break
224
+ }
225
+ line = str.slice(blockStart, n)
226
+ } else {
227
+ n = buffer.indexOf('\n', blockStart)
228
+ if (n === -1) {
229
+ break
230
+ }
231
+ const b = buffer.slice(blockStart, n)
232
+ line = decoder?.decode(b) ?? b.toString()
203
233
  }
204
- const b = buffer.slice(blockStart, n)
205
- const line = decoder?.decode(b) ?? b.toString()
206
234
 
235
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
207
236
  if (dpositions) {
208
- while (blockStart + c.minv.dataPosition >= dpositions[pos++]) {}
237
+ while (blockStart + c.minv.dataPosition >= dpositions[pos++]!) {}
209
238
  pos--
210
239
  }
211
240
 
@@ -218,48 +247,31 @@ export default class TabixIndexedFile {
218
247
  line,
219
248
  )
220
249
 
221
- // do a small check just to make sure that the lines are really sorted
222
- // by start coordinate
223
- if (
224
- previousStartCoordinate !== undefined &&
225
- startCoordinate !== undefined &&
226
- previousStartCoordinate > startCoordinate
227
- ) {
228
- throw new Error(
229
- `Lines not sorted by start coordinate (${previousStartCoordinate} > ${startCoordinate}), this file is not usable with Tabix.`,
230
- )
231
- }
232
- previousStartCoordinate = startCoordinate
233
-
234
250
  if (overlaps) {
235
251
  callback(
236
- line.trim(),
237
- // cpositions[pos] refers to actual file offset of a bgzip block boundaries
252
+ line,
253
+ // cpositions[pos] refers to actual file offset of a bgzip block
254
+ // boundaries
238
255
  //
239
- // we multiply by (1 <<8) in order to make sure each block has a "unique"
240
- // address space so that data in that block could never overlap
256
+ // we multiply by (1 <<8) in order to make sure each block has a
257
+ // "unique" address space so that data in that block could never
258
+ // overlap
241
259
  //
242
- // then the blockStart-dpositions is an uncompressed file offset from
243
- // that bgzip block boundary, and since the cpositions are multiplied by
244
- // (1 << 8) these uncompressed offsets get a unique space
245
- cpositions[pos] * (1 << 8) +
246
- (blockStart - dpositions[pos]) +
260
+ // then the blockStart-dpositions is an uncompressed file offset
261
+ // from that bgzip block boundary, and since the cpositions are
262
+ // multiplied by (1 << 8) these uncompressed offsets get a unique
263
+ // space
264
+ cpositions[pos]! * (1 << 8) +
265
+ (blockStart - dpositions[pos]!) +
247
266
  c.minv.dataPosition +
248
267
  1,
249
268
  )
250
269
  } else if (startCoordinate !== undefined && startCoordinate >= end) {
251
- // the lines were overlapping the region, but now have stopped, so
252
- // we must be at the end of the relevant data and we can stop
253
- // processing data now
270
+ // the lines were overlapping the region, but now have stopped, so we
271
+ // must be at the end of the relevant data and we can stop processing
272
+ // data now
254
273
  return
255
274
  }
256
-
257
- // yield if we have emitted beyond the yield limit
258
- if (this.yieldTime && last - Date.now() > this.yieldTime) {
259
- last = Date.now()
260
- checkAbortSignal(signal)
261
- await timeout(1)
262
- }
263
275
  blockStart = n + 1
264
276
  }
265
277
  }
@@ -270,14 +282,16 @@ export default class TabixIndexedFile {
270
282
  }
271
283
 
272
284
  /**
273
- * get a buffer containing the "header" region of
274
- * the file, which are the bytes up to the first
275
- * non-meta line
285
+ * get a buffer containing the "header" region of the file, which are the
286
+ * bytes up to the first non-meta line
276
287
  */
277
288
  async getHeaderBuffer(opts: Options = {}) {
278
289
  const { firstDataLine, metaChar, maxBlockSize } =
279
290
  await this.getMetadata(opts)
291
+
280
292
  checkAbortSignal(opts.signal)
293
+
294
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
281
295
  const maxFetch = (firstDataLine?.blockPosition || 0) + maxBlockSize
282
296
  // TODO: what if we don't have a firstDataLine, and the header
283
297
  // actually takes up more than one block? this case is not covered here
@@ -299,14 +313,14 @@ export default class TabixIndexedFile {
299
313
  lastNewline = i
300
314
  }
301
315
  }
302
- return bytes.slice(0, lastNewline + 1)
316
+ return bytes.subarray(0, lastNewline + 1)
303
317
  }
304
318
  return bytes
305
319
  }
306
320
 
307
321
  /**
308
- * get a string containing the "header" region of the
309
- * file, is the portion up to the first non-meta line
322
+ * get a string containing the "header" region of the file, is the portion up
323
+ * to the first non-meta line
310
324
  *
311
325
  * @returns {Promise} for a string
312
326
  */
@@ -316,9 +330,8 @@ export default class TabixIndexedFile {
316
330
  }
317
331
 
318
332
  /**
319
- * get an array of reference sequence names, in the order in which
320
- * they occur in the file. reference sequence renaming is not applied
321
- * to these names.
333
+ * get an array of reference sequence names, in the order in which they occur
334
+ * in the file. reference sequence renaming is not applied to these names.
322
335
  */
323
336
  async getReferenceSequenceNames(opts: Options = {}) {
324
337
  const metadata = await this.getMetadata(opts)
@@ -326,12 +339,17 @@ export default class TabixIndexedFile {
326
339
  }
327
340
 
328
341
  /**
329
- * @param {object} metadata metadata object from the parsed index,
330
- * containing columnNumbers, metaChar, and format
342
+ * @param {object} metadata metadata object from the parsed index, containing
343
+ * columnNumbers, metaChar, and format
344
+ *
331
345
  * @param {string} regionRefName
346
+ *
332
347
  * @param {number} regionStart region start coordinate (0-based-half-open)
348
+ *
333
349
  * @param {number} regionEnd region end coordinate (0-based-half-open)
350
+ *
334
351
  * @param {array[string]} line
352
+ *
335
353
  * @returns {object} like `{startCoordinate, overlaps}`. overlaps is boolean,
336
354
  * true if line is a data line that overlaps the given region
337
355
  */
@@ -364,22 +382,25 @@ export default class TabixIndexedFile {
364
382
  }
365
383
  const maxColumn = Math.max(ref, start, end)
366
384
 
367
- // this code is kind of complex, but it is fairly fast.
368
- // basically, we want to avoid doing a split, because if the lines are really long
369
- // that could lead to us allocating a bunch of extra memory, which is slow
385
+ // this code is kind of complex, but it is fairly fast. basically, we want
386
+ // to avoid doing a split, because if the lines are really long that could
387
+ // lead to us allocating a bunch of extra memory, which is slow
370
388
 
371
389
  let currentColumnNumber = 1 // cols are numbered starting at 1 in the index metadata
372
390
  let currentColumnStart = 0
373
391
  let refSeq = ''
374
392
  let startCoordinate = -Infinity
375
- for (let i = 0; i < line.length + 1; i += 1) {
376
- if (line[i] === '\t' || i === line.length) {
393
+ const l = line.length
394
+ for (let i = 0; i < l + 1; i++) {
395
+ if (line[i] === '\t' || i === l) {
377
396
  if (currentColumnNumber === ref) {
378
397
  if (
379
398
  this.renameRefSeq(line.slice(currentColumnStart, i)) !==
380
399
  regionRefName
381
400
  ) {
382
- return { overlaps: false }
401
+ return {
402
+ overlaps: false,
403
+ }
383
404
  }
384
405
  } else if (currentColumnNumber === start) {
385
406
  startCoordinate = parseInt(line.slice(currentColumnStart, i), 10)
@@ -388,12 +409,18 @@ export default class TabixIndexedFile {
388
409
  startCoordinate -= 1
389
410
  }
390
411
  if (startCoordinate >= regionEnd) {
391
- return { startCoordinate, overlaps: false }
412
+ return {
413
+ startCoordinate,
414
+ overlaps: false,
415
+ }
392
416
  }
393
417
  if (end === 0 || end === start) {
394
418
  // if we have no end, we assume the feature is 1 bp long
395
419
  if (startCoordinate + 1 <= regionStart) {
396
- return { startCoordinate, overlaps: false }
420
+ return {
421
+ startCoordinate,
422
+ overlaps: false,
423
+ }
397
424
  }
398
425
  }
399
426
  } else if (format === 'VCF' && currentColumnNumber === 4) {
@@ -407,9 +434,11 @@ export default class TabixIndexedFile {
407
434
  refSeq,
408
435
  line.slice(currentColumnStart, i),
409
436
  )
410
- : parseInt(line.slice(currentColumnStart, i), 10)
437
+ : Number.parseInt(line.slice(currentColumnStart, i), 10)
411
438
  if (endCoordinate <= regionStart) {
412
- return { overlaps: false }
439
+ return {
440
+ overlaps: false,
441
+ }
413
442
  }
414
443
  }
415
444
  currentColumnStart = i + 1
@@ -419,7 +448,10 @@ export default class TabixIndexedFile {
419
448
  }
420
449
  }
421
450
  }
422
- return { startCoordinate, overlaps: true }
451
+ return {
452
+ startCoordinate,
453
+ overlaps: true,
454
+ }
423
455
  }
424
456
 
425
457
  _getVcfEnd(startCoordinate: number, refSeq: string, info: any) {
@@ -450,8 +482,11 @@ export default class TabixIndexedFile {
450
482
  }
451
483
 
452
484
  /**
453
- * return the approximate number of data lines in the given reference sequence
485
+ * return the approximate number of data lines in the given reference
486
+ * sequence
487
+ *
454
488
  * @param refSeq reference sequence name
489
+ *
455
490
  * @returns number of data lines present on that reference sequence
456
491
  */
457
492
  async lineCount(refName: string, opts: Options = {}) {
@@ -468,7 +503,7 @@ export default class TabixIndexedFile {
468
503
  opts,
469
504
  )
470
505
 
471
- return buffer.slice(0, bytesRead)
506
+ return buffer.subarray(0, bytesRead)
472
507
  }
473
508
 
474
509
  /**
@@ -476,8 +511,8 @@ export default class TabixIndexedFile {
476
511
  * contiguous bgzip blocks) of the file
477
512
  */
478
513
  async readChunk(c: Chunk, opts: Options = {}) {
479
- // fetch the uncompressed data, uncompress carefully a block at a time,
480
- // and stop when done
514
+ // fetch the uncompressed data, uncompress carefully a block at a time, and
515
+ // stop when done
481
516
 
482
517
  const data = await this._readRegion(
483
518
  c.minv.blockPosition,
package/src/tbi.ts CHANGED
@@ -10,7 +10,8 @@ const TBI_MAGIC = 21578324 // TBI\1
10
10
  const TAD_LIDX_SHIFT = 14
11
11
 
12
12
  /**
13
- * calculate the list of bins that may overlap with region [beg,end) (zero-based half-open)
13
+ * calculate the list of bins that may overlap with region [beg,end)
14
+ * (zero-based half-open)
14
15
  */
15
16
  function reg2bins(beg: number, end: number) {
16
17
  beg += 1 // < convert to 1-based closed
@@ -22,16 +23,16 @@ function reg2bins(beg: number, end: number) {
22
23
  [73 + (beg >> 20), 73 + (end >> 20)],
23
24
  [585 + (beg >> 17), 585 + (end >> 17)],
24
25
  [4681 + (beg >> 14), 4681 + (end >> 14)],
25
- ]
26
+ ] as const
26
27
  }
27
28
 
28
29
  export default class TabixIndex extends IndexFile {
29
30
  async lineCount(refName: string, opts: Options = {}) {
30
31
  const indexData = await this.parse(opts)
31
- if (!indexData) {
32
+ const refId = indexData.refNameToId[refName]
33
+ if (refId === undefined) {
32
34
  return -1
33
35
  }
34
- const refId = indexData.refNameToId[refName]
35
36
  const idx = indexData.indices[refId]
36
37
  if (!idx) {
37
38
  return -1
@@ -194,10 +195,10 @@ export default class TabixIndex extends IndexFile {
194
195
  }
195
196
 
196
197
  const indexData = await this.parse(opts)
197
- if (!indexData) {
198
+ const refId = indexData.refNameToId[refName]
199
+ if (refId === undefined) {
198
200
  return []
199
201
  }
200
- const refId = indexData.refNameToId[refName]
201
202
  const ba = indexData.indices[refId]
202
203
  if (!ba) {
203
204
  return []
package/src/util.ts CHANGED
@@ -15,14 +15,15 @@ class AbortError extends Error {
15
15
  public code: string | undefined
16
16
  }
17
17
  /**
18
- * Properly check if the given AbortSignal is aborted.
19
- * Per the standard, if the signal reads as aborted,
20
- * this function throws either a DOMException AbortError, or a regular error
21
- * with a `code` attribute set to `ERR_ABORTED`.
18
+ * Properly check if the given AbortSignal is aborted. Per the standard, if the
19
+ * signal reads as aborted, this function throws either a DOMException
20
+ * AbortError, or a regular error with a `code` attribute set to `ERR_ABORTED`.
22
21
  *
23
22
  * For convenience, passing `undefined` is a no-op
24
23
  *
25
- * @param {AbortSignal} [signal] an AbortSignal, or anything with an `aborted` attribute
24
+ * @param {AbortSignal} [signal] an AbortSignal, or anything with an `aborted`
25
+ * attribute
26
+ *
26
27
  * @returns nothing
27
28
  */
28
29
  export function checkAbortSignal(signal?: AbortSignal) {
@@ -59,7 +60,7 @@ export function canMergeBlocks(chunk1: Chunk, chunk2: Chunk) {
59
60
  )
60
61
  }
61
62
 
62
- export function optimizeChunks(chunks: Chunk[], lowest: VirtualOffset) {
63
+ export function optimizeChunks(chunks: Chunk[], lowest?: VirtualOffset) {
63
64
  const mergedChunks: Chunk[] = []
64
65
  let lastChunk: Chunk | null = null
65
66
 
@@ -16,20 +16,6 @@ export default class VirtualOffset {
16
16
  this.blockPosition - b.blockPosition || this.dataPosition - b.dataPosition
17
17
  )
18
18
  }
19
-
20
- static min(...args: VirtualOffset[]) {
21
- let min
22
- let i = 0
23
- for (; !min; i += 1) {
24
- min = args[i]
25
- }
26
- for (; i < args.length; i += 1) {
27
- if (min.compareTo(args[i]) > 0) {
28
- min = args[i]
29
- }
30
- }
31
- return min
32
- }
33
19
  }
34
20
  export function fromBytes(bytes: Buffer, offset = 0, bigendian = false) {
35
21
  if (bigendian) {
@@ -37,12 +23,12 @@ export function fromBytes(bytes: Buffer, offset = 0, bigendian = false) {
37
23
  }
38
24
 
39
25
  return new VirtualOffset(
40
- bytes[offset + 7] * 0x10000000000 +
41
- bytes[offset + 6] * 0x100000000 +
42
- bytes[offset + 5] * 0x1000000 +
43
- bytes[offset + 4] * 0x10000 +
44
- bytes[offset + 3] * 0x100 +
45
- bytes[offset + 2],
46
- (bytes[offset + 1] << 8) | bytes[offset],
26
+ bytes[offset + 7]! * 0x10000000000 +
27
+ bytes[offset + 6]! * 0x100000000 +
28
+ bytes[offset + 5]! * 0x1000000 +
29
+ bytes[offset + 4]! * 0x10000 +
30
+ bytes[offset + 3]! * 0x100 +
31
+ bytes[offset + 2]!,
32
+ (bytes[offset + 1]! << 8) | bytes[offset]!,
47
33
  )
48
34
  }