@gmod/bbi 4.0.6 → 5.0.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/dist/bbi.d.ts +20 -4
  3. package/dist/bbi.js +123 -106
  4. package/dist/bbi.js.map +1 -1
  5. package/dist/bigbed.js +88 -69
  6. package/dist/bigbed.js.map +1 -1
  7. package/dist/bigint-polyfill/polyfill.js +0 -10
  8. package/dist/bigint-polyfill/polyfill.js.map +1 -1
  9. package/dist/bigint-polyfill/pure.d.ts +0 -2
  10. package/dist/bigint-polyfill/pure.js +0 -26
  11. package/dist/bigint-polyfill/pure.js.map +1 -1
  12. package/dist/bigwig.js +3 -8
  13. package/dist/bigwig.js.map +1 -1
  14. package/dist/block-view.d.ts +4 -6
  15. package/dist/block-view.js +122 -131
  16. package/dist/block-view.js.map +1 -1
  17. package/dist/range.js +1 -1
  18. package/dist/range.js.map +1 -1
  19. package/dist/util.d.ts +12 -14
  20. package/dist/util.js +8 -14
  21. package/dist/util.js.map +1 -1
  22. package/esm/bbi.d.ts +20 -4
  23. package/esm/bbi.js +123 -106
  24. package/esm/bbi.js.map +1 -1
  25. package/esm/bigbed.js +88 -69
  26. package/esm/bigbed.js.map +1 -1
  27. package/esm/bigint-polyfill/polyfill.js +1 -11
  28. package/esm/bigint-polyfill/polyfill.js.map +1 -1
  29. package/esm/bigint-polyfill/pure.d.ts +0 -2
  30. package/esm/bigint-polyfill/pure.js +0 -24
  31. package/esm/bigint-polyfill/pure.js.map +1 -1
  32. package/esm/bigwig.js +3 -8
  33. package/esm/bigwig.js.map +1 -1
  34. package/esm/block-view.d.ts +4 -6
  35. package/esm/block-view.js +122 -131
  36. package/esm/block-view.js.map +1 -1
  37. package/esm/range.js +1 -1
  38. package/esm/range.js.map +1 -1
  39. package/esm/util.d.ts +12 -14
  40. package/esm/util.js +8 -14
  41. package/esm/util.js.map +1 -1
  42. package/package.json +4 -5
  43. package/src/bbi.ts +149 -116
  44. package/src/bigbed.ts +99 -80
  45. package/src/bigint-polyfill/polyfill.ts +1 -13
  46. package/src/bigint-polyfill/pure.ts +0 -36
  47. package/src/bigwig.ts +3 -9
  48. package/src/block-view.ts +133 -168
  49. package/src/range.ts +1 -1
  50. package/src/util.ts +16 -21
package/src/bigwig.ts CHANGED
@@ -11,24 +11,18 @@ export class BigWig extends BBI {
11
11
  * or scale used to infer the zoomLevel to use
12
12
  */
13
13
  protected async getView(scale: number, opts: RequestOptions) {
14
- const { zoomLevels, refsByName, fileSize, isBigEndian, uncompressBufSize } =
14
+ const { zoomLevels, refsByName, isBigEndian, uncompressBufSize } =
15
15
  await this.getHeader(opts)
16
16
  const basesPerPx = 1 / scale
17
- let maxLevel = zoomLevels.length
18
- if (!fileSize) {
19
- // if we don't know the file size, we can't fetch the highest zoom level :-(
20
- maxLevel -= 1
21
- }
17
+ const maxLevel = zoomLevels.length - 1
22
18
 
23
19
  for (let i = maxLevel; i >= 0; i -= 1) {
24
20
  const zh = zoomLevels[i]
25
21
  if (zh && zh.reductionLevel <= 2 * basesPerPx) {
26
- const indexOffset = Number(zh.indexOffset)
27
-
28
22
  return new BlockView(
29
23
  this.bbi,
30
24
  refsByName,
31
- indexOffset,
25
+ zh.indexOffset,
32
26
  isBigEndian,
33
27
  uncompressBufSize > 0,
34
28
  'summary',
package/src/block-view.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Buffer } from 'buffer'
2
2
  import { Observer } from 'rxjs'
3
- import { Parser } from 'binary-parser'
4
3
  import AbortablePromiseCache from '@gmod/abortable-promise-cache'
5
4
  import { GenericFilehandle } from 'generic-filehandle'
6
5
  import QuickLRU from 'quick-lru'
@@ -16,23 +15,10 @@ interface CoordRequest {
16
15
  start: number
17
16
  end: number
18
17
  }
19
- interface DataBlock {
20
- blockOffset: bigint
21
- blockSize: bigint
22
- startChrom: number
23
- endChrom: number
24
- startBase: number
25
- endBase: number
26
- validCnt: number
27
- minVal: number
28
- maxVal: number
29
- sumData: number
30
- sumSqData: number
31
- }
32
18
 
33
19
  interface ReadData {
34
- offset: bigint | number
35
- length: bigint | number
20
+ offset: number
21
+ length: number
36
22
  }
37
23
 
38
24
  interface Options {
@@ -40,115 +26,15 @@ interface Options {
40
26
  request?: CoordRequest
41
27
  }
42
28
 
43
- const BIG_WIG_TYPE_GRAPH = 1
44
- const BIG_WIG_TYPE_VSTEP = 2
45
- const BIG_WIG_TYPE_FSTEP = 3
46
-
47
29
  function coordFilter(s1: number, e1: number, s2: number, e2: number): boolean {
48
30
  return s1 < e2 && e1 >= s2
49
31
  }
50
32
 
51
- function getParsers(isBigEndian: boolean) {
52
- const le = isBigEndian ? 'big' : 'little'
53
- const summaryParser = new Parser()
54
- .endianess(le)
55
- .uint32('chromId')
56
- .uint32('start')
57
- .uint32('end')
58
- .uint32('validCnt')
59
- .floatle('minScore')
60
- .floatle('maxScore')
61
- .floatle('sumData')
62
- .floatle('sumSqData')
63
- .saveOffset('offset')
64
-
65
- const leafParser = new Parser()
66
- .endianess(le)
67
- .uint8('isLeaf')
68
- .skip(1)
69
- .uint16('cnt')
70
- .choice({
71
- tag: 'isLeaf',
72
- choices: {
73
- 1: new Parser().endianess(le).array('blocksToFetch', {
74
- length: 'cnt',
75
- type: new Parser()
76
- .endianess(le)
77
- .uint32('startChrom')
78
- .uint32('startBase')
79
- .uint32('endChrom')
80
- .uint32('endBase')
81
- .uint64('blockOffset')
82
- .uint64('blockSize')
83
- .saveOffset('offset'),
84
- }),
85
- 0: new Parser().array('recurOffsets', {
86
- length: 'cnt',
87
- type: new Parser()
88
- .endianess(le)
89
- .uint32('startChrom')
90
- .uint32('startBase')
91
- .uint32('endChrom')
92
- .uint32('endBase')
93
- .uint64('blockOffset')
94
- .saveOffset('offset'),
95
- }),
96
- },
97
- })
98
- const bigBedParser = new Parser()
99
- .endianess(le)
100
- .uint32('chromId')
101
- .int32('start')
102
- .int32('end')
103
- .string('rest', {
104
- zeroTerminated: true,
105
- })
106
- .saveOffset('offset')
107
-
108
- const bigWigParser = new Parser()
109
- .endianess(le)
110
- .skip(4)
111
- .int32('blockStart')
112
- .skip(4)
113
- .uint32('itemStep')
114
- .uint32('itemSpan')
115
- .uint8('blockType')
116
- .skip(1)
117
- .uint16('itemCount')
118
- .choice({
119
- tag: 'blockType',
120
- choices: {
121
- [BIG_WIG_TYPE_FSTEP]: new Parser().array('items', {
122
- length: 'itemCount',
123
- type: new Parser().floatle('score'),
124
- }),
125
- [BIG_WIG_TYPE_VSTEP]: new Parser().array('items', {
126
- length: 'itemCount',
127
- type: new Parser().endianess(le).int32('start').floatle('score'),
128
- }),
129
- [BIG_WIG_TYPE_GRAPH]: new Parser().array('items', {
130
- length: 'itemCount',
131
- type: new Parser()
132
- .endianess(le)
133
- .int32('start')
134
- .int32('end')
135
- .floatle('score'),
136
- }),
137
- },
138
- })
139
- return {
140
- bigWigParser,
141
- bigBedParser,
142
- summaryParser,
143
- leafParser,
144
- }
145
- }
146
-
147
33
  /**
148
34
  * View into a subset of the data in a BigWig file.
149
35
  *
150
- * Adapted by Robert Buels and Colin Diesh from bigwig.js in the Dalliance Genome
151
- * Explorer by Thomas Down.
36
+ * Adapted by Robert Buels and Colin Diesh from bigwig.js in the Dalliance
37
+ * Genome Explorer by Thomas Down.
152
38
  * @constructs
153
39
  */
154
40
 
@@ -159,8 +45,8 @@ export class BlockView {
159
45
  cache: new QuickLRU({ maxSize: 1000 }),
160
46
 
161
47
  fill: async (requestData, signal) => {
162
- const len = Number(requestData.length)
163
- const off = Number(requestData.offset)
48
+ const len = requestData.length
49
+ const off = requestData.offset
164
50
  const { buffer } = await this.bbi.read(Buffer.alloc(len), 0, len, off, {
165
51
  signal,
166
52
  })
@@ -168,10 +54,6 @@ export class BlockView {
168
54
  },
169
55
  })
170
56
 
171
- private leafParser: ReturnType<typeof getParsers>['leafParser']
172
-
173
- private bigBedParser: ReturnType<typeof getParsers>['bigBedParser']
174
-
175
57
  public constructor(
176
58
  private bbi: GenericFilehandle,
177
59
  private refsByName: any,
@@ -183,10 +65,6 @@ export class BlockView {
183
65
  if (!(cirTreeOffset >= 0)) {
184
66
  throw new Error('invalid cirTreeOffset!')
185
67
  }
186
-
187
- const parsers = getParsers(isBigEndian)
188
- this.leafParser = parsers.leafParser
189
- this.bigBedParser = parsers.bigBedParser
190
68
  }
191
69
 
192
70
  public async readWigData(
@@ -208,7 +86,7 @@ export class BlockView {
208
86
  Buffer.alloc(48),
209
87
  0,
210
88
  48,
211
- Number(cirTreeOffset),
89
+ cirTreeOffset,
212
90
  opts,
213
91
  )
214
92
  }
@@ -218,35 +96,84 @@ export class BlockView {
218
96
  : buffer.readUInt32LE(4)
219
97
  let blocksToFetch: any[] = []
220
98
  let outstanding = 0
99
+ const le = true
221
100
 
222
101
  const cirFobRecur2 = (
223
102
  cirBlockData: Buffer,
224
- offset: number,
103
+ offset2: number,
225
104
  level: number,
226
105
  ) => {
227
106
  try {
228
- const data = cirBlockData.subarray(offset)
229
-
230
- const p = this.leafParser.parse(data) as {
231
- blocksToFetch: DataBlock[]
232
- recurOffsets: DataBlock[]
233
- }
234
- if (p.blocksToFetch) {
107
+ const data = cirBlockData.subarray(offset2)
108
+
109
+ const b = data
110
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length)
111
+ let offset = 0
112
+
113
+ const isLeaf = dataView.getUint8(offset)
114
+ offset += 2 // 1 skip
115
+ const cnt = dataView.getUint16(offset, le)
116
+ offset += 2
117
+ if (isLeaf === 1) {
118
+ const blocksToFetch2 = []
119
+ for (let i = 0; i < cnt; i++) {
120
+ const startChrom = dataView.getUint32(offset, le)
121
+ offset += 4
122
+ const startBase = dataView.getUint32(offset, le)
123
+ offset += 4
124
+ const endChrom = dataView.getUint32(offset, le)
125
+ offset += 4
126
+ const endBase = dataView.getUint32(offset, le)
127
+ offset += 4
128
+ const blockOffset = Number(dataView.getBigUint64(offset, le))
129
+ offset += 8
130
+ const blockSize = Number(dataView.getBigUint64(offset, le))
131
+ offset += 8
132
+ blocksToFetch2.push({
133
+ startChrom,
134
+ startBase,
135
+ endBase,
136
+ endChrom,
137
+ blockOffset,
138
+ blockSize,
139
+ offset,
140
+ })
141
+ }
235
142
  blocksToFetch = blocksToFetch.concat(
236
- p.blocksToFetch
143
+ blocksToFetch2
237
144
  .filter(f => filterFeats(f))
238
145
  .map(l => ({
239
146
  offset: l.blockOffset,
240
147
  length: l.blockSize,
241
148
  })),
242
149
  )
243
- }
244
- if (p.recurOffsets) {
245
- const recurOffsets = p.recurOffsets
150
+ } else if (isLeaf === 0) {
151
+ const recurOffsets = []
152
+ for (let i = 0; i < cnt; i++) {
153
+ const startChrom = dataView.getUint32(offset, le)
154
+ offset += 4
155
+ const startBase = dataView.getUint32(offset, le)
156
+ offset += 4
157
+ const endChrom = dataView.getUint32(offset, le)
158
+ offset += 4
159
+ const endBase = dataView.getUint32(offset, le)
160
+ offset += 4
161
+ const blockOffset = Number(dataView.getBigUint64(offset, le))
162
+ offset += 8
163
+ recurOffsets.push({
164
+ startChrom,
165
+ startBase,
166
+ endChrom,
167
+ endBase,
168
+ blockOffset,
169
+ offset,
170
+ })
171
+ }
172
+ const recurOffsets2 = recurOffsets
246
173
  .filter(f => filterFeats(f))
247
- .map(l => Number(l.blockOffset))
248
- if (recurOffsets.length > 0) {
249
- cirFobRecur(recurOffsets, level + 1)
174
+ .map(l => l.blockOffset)
175
+ if (recurOffsets2.length > 0) {
176
+ cirFobRecur(recurOffsets2, level + 1)
250
177
  }
251
178
  }
252
179
  } catch (e) {
@@ -254,7 +181,12 @@ export class BlockView {
254
181
  }
255
182
  }
256
183
 
257
- const filterFeats = (b: DataBlock) => {
184
+ const filterFeats = (b: {
185
+ startChrom: number
186
+ startBase: number
187
+ endChrom: number
188
+ endBase: number
189
+ }) => {
258
190
  const { startChrom, startBase, endChrom, endBase } = b
259
191
  return (
260
192
  (startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
@@ -296,13 +228,19 @@ export class BlockView {
296
228
  outstanding += offset.length
297
229
 
298
230
  // Upper bound on size, based on a completely full leaf node.
299
- const maxCirBlockSpan = 4 + Number(cirBlockSize) * 32
231
+ const maxCirBlockSpan = 4 + cirBlockSize * 32
300
232
  let spans = new Range([
301
- { min: offset[0], max: offset[0] + maxCirBlockSpan },
233
+ {
234
+ min: offset[0],
235
+ max: offset[0] + maxCirBlockSpan,
236
+ },
302
237
  ])
303
238
  for (let i = 1; i < offset.length; i += 1) {
304
239
  const blockSpan = new Range([
305
- { min: offset[i], max: offset[i] + maxCirBlockSpan },
240
+ {
241
+ min: offset[i],
242
+ max: offset[i] + maxCirBlockSpan,
243
+ },
306
244
  ])
307
245
  spans = spans.union(blockSpan)
308
246
  }
@@ -320,19 +258,15 @@ export class BlockView {
320
258
  }
321
259
 
322
260
  private parseSummaryBlock(
323
- buffer: Buffer,
261
+ b: Buffer,
324
262
  startOffset: number,
325
263
  request?: CoordRequest,
326
264
  ) {
327
265
  const features = [] as any[]
328
266
  let offset = startOffset
329
267
 
330
- const dataView = new DataView(
331
- buffer.buffer,
332
- buffer.byteOffset,
333
- buffer.length,
334
- )
335
- while (offset < buffer.byteLength) {
268
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length)
269
+ while (offset < b.byteLength) {
336
270
  // this was extracted from looking at the runtime code generated by
337
271
  // binary-parser
338
272
  const chromId = dataView.getUint32(offset, true)
@@ -381,11 +315,32 @@ export class BlockView {
381
315
  ) {
382
316
  const items = [] as Feature[]
383
317
  let currOffset = startOffset
318
+ const le = true
319
+ const b = data
320
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length)
384
321
  while (currOffset < data.byteLength) {
385
- const res = this.bigBedParser.parse(data.subarray(currOffset))
386
- res.uniqueId = `bb-${offset + currOffset}`
387
- items.push(res)
388
- currOffset += res.offset
322
+ const c2 = currOffset
323
+ const chromId = dataView.getUint32(currOffset, le)
324
+ currOffset += 4
325
+ const start = dataView.getInt32(currOffset, le)
326
+ currOffset += 4
327
+ const end = dataView.getInt32(currOffset, le)
328
+ currOffset += 4
329
+ let i = currOffset
330
+ for (; i < data.length; i++) {
331
+ if (data[i] === 0) {
332
+ break
333
+ }
334
+ }
335
+ const rest = data.subarray(currOffset, i).toString()
336
+ currOffset = i + 1
337
+ items.push({
338
+ chromId,
339
+ start,
340
+ end,
341
+ rest,
342
+ uniqueId: `bb-${offset + c2}`,
343
+ })
389
344
  }
390
345
 
391
346
  return request
@@ -398,7 +353,7 @@ export class BlockView {
398
353
  private parseBigWigBlock(
399
354
  buffer: Buffer,
400
355
  startOffset: number,
401
- request?: CoordRequest,
356
+ req?: CoordRequest,
402
357
  ) {
403
358
  const b = buffer.subarray(startOffset)
404
359
 
@@ -425,7 +380,11 @@ export class BlockView {
425
380
  offset += 4
426
381
  const score = dataView.getFloat32(offset, true)
427
382
  offset += 4
428
- items[i] = { start, end, score }
383
+ items[i] = {
384
+ start,
385
+ end,
386
+ score,
387
+ }
429
388
  }
430
389
  break
431
390
  }
@@ -435,7 +394,11 @@ export class BlockView {
435
394
  offset += 4
436
395
  const score = dataView.getFloat32(offset, true)
437
396
  offset += 4
438
- items[i] = { score, start, end: start + itemSpan }
397
+ items[i] = {
398
+ score,
399
+ start,
400
+ end: start + itemSpan,
401
+ }
439
402
  }
440
403
  break
441
404
  }
@@ -444,22 +407,24 @@ export class BlockView {
444
407
  const score = dataView.getFloat32(offset, true)
445
408
  offset += 4
446
409
  const start = blockStart + i * itemStep
447
- items[i] = { score, start, end: start + itemSpan }
410
+ items[i] = {
411
+ score,
412
+ start,
413
+ end: start + itemSpan,
414
+ }
448
415
  }
449
416
  break
450
417
  }
451
418
  }
452
419
 
453
- return request
454
- ? items.filter((f: any) =>
455
- coordFilter(f.start, f.end, request.start, request.end),
456
- )
420
+ return req
421
+ ? items.filter(f => coordFilter(f.start, f.end, req.start, req.end))
457
422
  : items
458
423
  }
459
424
 
460
425
  public async readFeatures(
461
426
  observer: Observer<Feature[]>,
462
- blocks: { offset: bigint; length: bigint }[],
427
+ blocks: { offset: number; length: number }[],
463
428
  opts: Options = {},
464
429
  ) {
465
430
  try {
package/src/range.ts CHANGED
@@ -19,7 +19,7 @@ export default class Range {
19
19
  }
20
20
 
21
21
  get max() {
22
- return this.ranges[this.ranges.length - 1].max
22
+ return this.ranges.at(-1)!.max
23
23
  }
24
24
 
25
25
  public contains(pos: number) {
package/src/util.ts CHANGED
@@ -6,26 +6,23 @@ export class AbortError extends Error {
6
6
  this.code = 'ERR_ABORTED'
7
7
  }
8
8
  }
9
+
10
+ interface Block {
11
+ offset: number
12
+ length: number
13
+ }
9
14
  // sort blocks by file offset and
10
15
  // group blocks that are within 2KB of eachother
11
- export function groupBlocks(blocks: { offset: bigint; length: bigint }[]) {
12
- blocks.sort((b0, b1) => Number(b0.offset) - Number(b1.offset))
16
+ export function groupBlocks(blocks: Block[]) {
17
+ blocks.sort((b0, b1) => b0.offset - b1.offset)
13
18
 
14
19
  const blockGroups = []
15
- let lastBlock
16
- let lastBlockEnd
20
+ let lastBlock: (Block & { blocks: Block[] }) | undefined
21
+ let lastBlockEnd: number | undefined
17
22
  for (const block of blocks) {
18
- if (
19
- lastBlock &&
20
- lastBlockEnd &&
21
- Number(block.offset) - lastBlockEnd <= 2000
22
- ) {
23
- lastBlock.length = BigInt(
24
- Number(lastBlock.length) +
25
- Number(block.length) -
26
- lastBlockEnd +
27
- Number(block.offset),
28
- )
23
+ if (lastBlock && lastBlockEnd && block.offset - lastBlockEnd <= 2000) {
24
+ lastBlock.length =
25
+ lastBlock.length + block.length - lastBlockEnd + block.offset
29
26
  lastBlock.blocks.push(block)
30
27
  } else {
31
28
  blockGroups.push(
@@ -36,17 +33,16 @@ export function groupBlocks(blocks: { offset: bigint; length: bigint }[]) {
36
33
  }),
37
34
  )
38
35
  }
39
- lastBlockEnd = Number(lastBlock.offset) + Number(lastBlock.length)
36
+ lastBlockEnd = lastBlock.offset + lastBlock.length
40
37
  }
41
38
 
42
39
  return blockGroups
43
40
  }
44
41
 
45
42
  /**
46
- * Properly check if the given AbortSignal is aborted.
47
- * Per the standard, if the signal reads as aborted,
48
- * this function throws either a DOMException AbortError, or a regular error
49
- * with a `code` attribute set to `ERR_ABORTED`.
43
+ * Properly check if the given AbortSignal is aborted. Per the standard, if the
44
+ * signal reads as aborted, this function throws either a DOMException
45
+ * AbortError, or a regular error with a `code` attribute set to `ERR_ABORTED`.
50
46
  *
51
47
  * For convenience, passing `undefined` is a no-op
52
48
  *
@@ -59,7 +55,6 @@ export function checkAbortSignal(signal?: AbortSignal): void {
59
55
  }
60
56
 
61
57
  if (signal.aborted) {
62
- // console.log('bam aborted!')
63
58
  if (typeof DOMException === 'undefined') {
64
59
  const e = new AbortError('aborted')
65
60
  e.code = 'ERR_ABORTED'