@gmod/bbi 7.0.5 → 8.0.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 (90) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +59 -0
  3. package/dist/bbi.d.ts +13 -3
  4. package/dist/bbi.js +81 -18
  5. package/dist/bbi.js.map +1 -1
  6. package/dist/bigbed.d.ts +14 -2
  7. package/dist/bigbed.js +127 -86
  8. package/dist/bigbed.js.map +1 -1
  9. package/dist/bigwig.js +1 -2
  10. package/dist/bigwig.js.map +1 -1
  11. package/dist/block-view.d.ts +13 -4
  12. package/dist/block-view.js +332 -150
  13. package/dist/block-view.js.map +1 -1
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.js +3 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/parse-bigwig.d.ts +3 -0
  18. package/dist/parse-bigwig.js +15 -0
  19. package/dist/parse-bigwig.js.map +1 -0
  20. package/dist/range.js +12 -24
  21. package/dist/range.js.map +1 -1
  22. package/dist/types.d.ts +14 -2
  23. package/dist/unzip.d.ts +18 -1
  24. package/dist/unzip.js +33 -4
  25. package/dist/unzip.js.map +1 -1
  26. package/dist/util.d.ts +2 -4
  27. package/dist/util.js +7 -5
  28. package/dist/util.js.map +1 -1
  29. package/dist/wasm/inflate-wasm-inlined.d.ts +19 -0
  30. package/dist/wasm/inflate-wasm-inlined.js +117 -0
  31. package/dist/wasm/inflate-wasm-inlined.js.map +1 -0
  32. package/dist/wasm/inflate_wasm.d.ts +1 -0
  33. package/dist/wasm/inflate_wasm.js +43 -0
  34. package/dist/wasm/inflate_wasm.js.map +1 -0
  35. package/dist/wasm/inflate_wasm_bg.d.ts +68 -0
  36. package/dist/wasm/inflate_wasm_bg.js +307 -0
  37. package/dist/wasm/inflate_wasm_bg.js.map +1 -0
  38. package/esm/bbi.d.ts +13 -3
  39. package/esm/bbi.js +81 -18
  40. package/esm/bbi.js.map +1 -1
  41. package/esm/bigbed.d.ts +14 -2
  42. package/esm/bigbed.js +127 -86
  43. package/esm/bigbed.js.map +1 -1
  44. package/esm/bigwig.js +1 -2
  45. package/esm/bigwig.js.map +1 -1
  46. package/esm/block-view.d.ts +13 -4
  47. package/esm/block-view.js +334 -152
  48. package/esm/block-view.js.map +1 -1
  49. package/esm/index.d.ts +2 -1
  50. package/esm/index.js +1 -0
  51. package/esm/index.js.map +1 -1
  52. package/esm/parse-bigwig.d.ts +3 -0
  53. package/esm/parse-bigwig.js +12 -0
  54. package/esm/parse-bigwig.js.map +1 -0
  55. package/esm/range.js +12 -24
  56. package/esm/range.js.map +1 -1
  57. package/esm/types.d.ts +14 -2
  58. package/esm/unzip.d.ts +18 -1
  59. package/esm/unzip.js +30 -3
  60. package/esm/unzip.js.map +1 -1
  61. package/esm/util.d.ts +2 -4
  62. package/esm/util.js +7 -5
  63. package/esm/util.js.map +1 -1
  64. package/esm/wasm/inflate-wasm-inlined.d.ts +19 -0
  65. package/esm/wasm/inflate-wasm-inlined.js +111 -0
  66. package/esm/wasm/inflate-wasm-inlined.js.map +1 -0
  67. package/esm/wasm/inflate_wasm.d.ts +1 -0
  68. package/esm/wasm/inflate_wasm.js +5 -0
  69. package/esm/wasm/inflate_wasm.js.map +1 -0
  70. package/esm/wasm/inflate_wasm_bg.d.ts +68 -0
  71. package/esm/wasm/inflate_wasm_bg.js +296 -0
  72. package/esm/wasm/inflate_wasm_bg.js.map +1 -0
  73. package/package.json +21 -17
  74. package/src/bbi.ts +101 -21
  75. package/src/bigbed.ts +165 -83
  76. package/src/bigwig.ts +1 -2
  77. package/src/block-view.ts +415 -158
  78. package/src/index.ts +8 -1
  79. package/src/parse-bigwig.ts +19 -0
  80. package/src/range.ts +13 -21
  81. package/src/types.ts +19 -2
  82. package/src/unzip.ts +88 -3
  83. package/src/util.ts +9 -10
  84. package/src/wasm/inflate-wasm-inlined.d.ts +49 -0
  85. package/src/wasm/inflate-wasm-inlined.js +1 -0
  86. package/src/wasm/inflate_wasm.d.ts +35 -0
  87. package/src/wasm/inflate_wasm.js +4 -0
  88. package/src/wasm/inflate_wasm_bg.js +309 -0
  89. package/src/wasm/inflate_wasm_bg.wasm +0 -0
  90. package/src/wasm/inflate_wasm_bg.wasm.d.ts +13 -0
package/src/bbi.ts CHANGED
@@ -5,6 +5,7 @@ import { toArray } from 'rxjs/operators'
5
5
  import { BlockView } from './block-view.ts'
6
6
 
7
7
  import type {
8
+ BigWigFeatureArrays,
8
9
  BigWigHeader,
9
10
  BigWigHeaderWithRefNames,
10
11
  Feature,
@@ -12,6 +13,7 @@ import type {
12
13
  RequestOptions2,
13
14
  RequestOptions,
14
15
  Statistics,
16
+ SummaryFeatureArrays,
15
17
  ZoomLevel,
16
18
  } from './types.ts'
17
19
  import type { GenericFilehandle } from 'generic-filehandle2'
@@ -19,6 +21,8 @@ import type { GenericFilehandle } from 'generic-filehandle2'
19
21
  const BIG_WIG_MAGIC = -2003829722
20
22
  const BIG_BED_MAGIC = -2021002517
21
23
 
24
+ const decoder = new TextDecoder('utf8')
25
+
22
26
  function getDataView(buffer: Uint8Array) {
23
27
  return new DataView(buffer.buffer, buffer.byteOffset, buffer.length)
24
28
  }
@@ -71,7 +75,7 @@ export abstract class BBI {
71
75
 
72
76
  private async _getHeader(opts?: RequestOptions) {
73
77
  const header = await this._getMainHeader(opts)
74
- const chroms = await this._readChromTree(header, opts)
78
+ const chroms = await this._readChromosomeTree(header, opts)
75
79
  return {
76
80
  ...header,
77
81
  ...chroms,
@@ -96,7 +100,8 @@ export abstract class BBI {
96
100
  offset += 2
97
101
  const numZoomLevels = dataView.getUint16(offset, true)
98
102
  offset += 2
99
- const chromTreeOffset = Number(dataView.getBigUint64(offset, true))
103
+ // Offset to the B+ tree that maps chromosome names to integer IDs
104
+ const chromosomeTreeOffset = Number(dataView.getBigUint64(offset, true))
100
105
  offset += 8
101
106
  const unzoomedDataOffset = Number(dataView.getBigUint64(offset, true))
102
107
  offset += 8
@@ -142,7 +147,7 @@ export abstract class BBI {
142
147
 
143
148
  let totalSummary: Statistics
144
149
  if (totalSummaryOffset) {
145
- const b2 = b.subarray(Number(totalSummaryOffset))
150
+ const b2 = b.subarray(totalSummaryOffset)
146
151
  let offset = 0
147
152
  const dataView = getDataView(b2)
148
153
  const basesCovered = Number(dataView.getBigUint64(offset, true))
@@ -166,7 +171,6 @@ export abstract class BBI {
166
171
  } else {
167
172
  throw new Error('no stats')
168
173
  }
169
- const decoder = new TextDecoder('utf8')
170
174
 
171
175
  return {
172
176
  zoomLevels,
@@ -178,7 +182,7 @@ export abstract class BBI {
178
182
  definedFieldCount,
179
183
  uncompressBufSize,
180
184
  asOffset,
181
- chromTreeOffset,
185
+ chromosomeTreeOffset,
182
186
  totalSummaryOffset,
183
187
  unzoomedDataOffset,
184
188
  unzoomedIndexOffset,
@@ -190,16 +194,21 @@ export abstract class BBI {
190
194
  }
191
195
  }
192
196
 
193
- private async _readChromTree(
197
+ // Reads the B+ tree that maps chromosome names to integer IDs
198
+ // This is part of the "cirTree" (combined ID R-tree) structure, which uses
199
+ // integer chromosome IDs instead of strings for more efficient spatial indexing
200
+ private async _readChromosomeTree(
194
201
  header: BigWigHeader,
195
202
  opts?: { signal?: AbortSignal },
196
203
  ) {
197
- const refsByNumber: Record<number, RefInfo> = []
198
- const refsByName: Record<string, number> = {}
204
+ const refsByNumber: RefInfo[] = []
205
+ const refsByName = {} as Record<string, number>
199
206
 
200
- const chromTreeOffset = Number(header.chromTreeOffset)
207
+ const chromosomeTreeOffset = header.chromosomeTreeOffset
201
208
 
202
- const dataView = getDataView(await this.bbi.read(32, chromTreeOffset, opts))
209
+ const dataView = getDataView(
210
+ await this.bbi.read(32, chromosomeTreeOffset, opts),
211
+ )
203
212
  let offset = 0
204
213
  // const magic = dataView.getUint32(offset, true) // unused
205
214
  offset += 4
@@ -212,9 +221,8 @@ export abstract class BBI {
212
221
  // const itemCount = dataView.getBigUint64(offset, true) // unused
213
222
  offset += 8
214
223
 
215
- const decoder = new TextDecoder('utf8')
216
-
217
- const bptReadNode = async (currentOffset: number) => {
224
+ // Recursively traverses the B+ tree to populate chromosome name-to-ID mappings
225
+ const readBPlusTreeNode = async (currentOffset: number) => {
218
226
  const b = await this.bbi.read(4, currentOffset)
219
227
  const dataView = getDataView(b)
220
228
  let offset = 0
@@ -225,6 +233,7 @@ export abstract class BBI {
225
233
  const count = dataView.getUint16(offset, true)
226
234
  offset += 2
227
235
 
236
+ // Leaf nodes contain the actual chromosome name-to-ID mappings
228
237
  if (isLeafNode) {
229
238
  const b = await this.bbi.read(
230
239
  count * (keySize + valSize),
@@ -234,9 +243,10 @@ export abstract class BBI {
234
243
  offset = 0
235
244
 
236
245
  for (let n = 0; n < count; n++) {
237
- const key = decoder
238
- .decode(b.subarray(offset, offset + keySize))
239
- .replaceAll('\0', '')
246
+ const keyEnd = b.indexOf(0, offset)
247
+ const key = decoder.decode(
248
+ b.subarray(offset, keyEnd !== -1 ? keyEnd : offset + keySize),
249
+ )
240
250
  offset += keySize
241
251
  const refId = dataView.getUint32(offset, true)
242
252
  offset += 4
@@ -251,6 +261,7 @@ export abstract class BBI {
251
261
  }
252
262
  }
253
263
  } else {
264
+ // Non-leaf nodes contain pointers to child nodes
254
265
  const nextNodes = []
255
266
  const dataView = getDataView(
256
267
  await this.bbi.read(count * (keySize + 8), currentOffset + offset),
@@ -261,12 +272,12 @@ export abstract class BBI {
261
272
  offset += keySize
262
273
  const childOffset = Number(dataView.getBigUint64(offset, true))
263
274
  offset += 8
264
- nextNodes.push(bptReadNode(childOffset))
275
+ nextNodes.push(readBPlusTreeNode(childOffset))
265
276
  }
266
277
  await Promise.all(nextNodes)
267
278
  }
268
279
  }
269
- await bptReadNode(chromTreeOffset + 32)
280
+ await readBPlusTreeNode(chromosomeTreeOffset + 32)
270
281
  return {
271
282
  refsByName,
272
283
  refsByNumber,
@@ -284,7 +295,7 @@ export abstract class BBI {
284
295
  this.bbi,
285
296
  refsByName,
286
297
  unzoomedIndexOffset,
287
- uncompressBufSize > 0,
298
+ uncompressBufSize,
288
299
  fileType,
289
300
  )
290
301
  }
@@ -345,7 +356,76 @@ export abstract class BBI {
345
356
  ) {
346
357
  const ob = await this.getFeatureStream(refName, start, end, opts)
347
358
 
348
- const ret = await firstValueFrom(ob.pipe(toArray()))
349
- return ret.flat()
359
+ const arrays = await firstValueFrom(ob.pipe(toArray()))
360
+ const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0)
361
+ const result = new Array(totalLength)
362
+ let index = 0
363
+ for (const arr of arrays) {
364
+ for (const item of arr) {
365
+ result[index++] = item
366
+ }
367
+ }
368
+ return result
369
+ }
370
+
371
+ /**
372
+ * Gets features from a BigWig file as typed arrays (more efficient than getFeatures)
373
+ *
374
+ * @param refName - The chromosome name
375
+ * @param start - The start of a region
376
+ * @param end - The end of a region
377
+ * @param opts - Options including basesPerSpan or scale
378
+ * @returns Promise with typed arrays: starts, ends, scores (and minScores/maxScores for summary data)
379
+ */
380
+ public async getFeaturesAsArrays(
381
+ refName: string,
382
+ start: number,
383
+ end: number,
384
+ opts?: RequestOptions2,
385
+ ): Promise<BigWigFeatureArrays | SummaryFeatureArrays> {
386
+ const features = await this.getFeatures(refName, start, end, opts)
387
+ const count = features.length
388
+
389
+ if (count === 0) {
390
+ return {
391
+ starts: new Int32Array(0),
392
+ ends: new Int32Array(0),
393
+ scores: new Float32Array(0),
394
+ }
395
+ }
396
+
397
+ const hasSummary = features[0]?.summary === true
398
+
399
+ if (hasSummary) {
400
+ const starts = new Int32Array(count)
401
+ const ends = new Int32Array(count)
402
+ const scores = new Float32Array(count)
403
+ const minScores = new Float32Array(count)
404
+ const maxScores = new Float32Array(count)
405
+
406
+ for (let i = 0; i < count; i++) {
407
+ const f = features[i]!
408
+ starts[i] = f.start
409
+ ends[i] = f.end
410
+ scores[i] = f.score ?? 0
411
+ minScores[i] = f.minScore ?? 0
412
+ maxScores[i] = f.maxScore ?? 0
413
+ }
414
+
415
+ return { starts, ends, scores, minScores, maxScores }
416
+ }
417
+
418
+ const starts = new Int32Array(count)
419
+ const ends = new Int32Array(count)
420
+ const scores = new Float32Array(count)
421
+
422
+ for (let i = 0; i < count; i++) {
423
+ const f = features[i]!
424
+ starts[i] = f.start
425
+ ends[i] = f.end
426
+ scores[i] = f.score ?? 0
427
+ }
428
+
429
+ return { starts, ends, scores }
350
430
  }
351
431
  }
package/src/bigbed.ts CHANGED
@@ -6,6 +6,9 @@ import { map, reduce } from 'rxjs/operators'
6
6
  import { BBI } from './bbi.ts'
7
7
 
8
8
  import type { Feature, RequestOptions } from './types.ts'
9
+ import type { GenericFilehandle } from 'generic-filehandle2'
10
+
11
+ const decoder = new TextDecoder('utf8')
9
12
 
10
13
  interface Loc {
11
14
  key: string
@@ -25,6 +28,133 @@ export function filterUndef<T>(ts: (T | undefined)[]): T[] {
25
28
  return ts.filter((t: T | undefined): t is T => !!t)
26
29
  }
27
30
 
31
+ function getTabField(str: string, fieldIndex: number) {
32
+ if (fieldIndex < 0) {
33
+ return undefined
34
+ }
35
+ let start = 0
36
+ for (let i = 0; i < fieldIndex; i++) {
37
+ start = str.indexOf('\t', start)
38
+ if (start === -1) {
39
+ return undefined
40
+ }
41
+ start++
42
+ }
43
+ const end = str.indexOf('\t', start)
44
+ return end === -1 ? str.slice(start) : str.slice(start, end)
45
+ }
46
+
47
+ // Parses a null-terminated string key from a B+ tree node
48
+ function parseKey(buffer: Uint8Array, offset: number, keySize: number) {
49
+ const keyEnd = buffer.indexOf(0, offset)
50
+ return decoder.decode(
51
+ buffer.subarray(offset, keyEnd !== -1 ? keyEnd : offset + keySize),
52
+ )
53
+ }
54
+
55
+ // Recursively traverses a B+ tree to search for a specific name in the BigBed extraIndex
56
+ // B+ trees are balanced tree structures optimized for disk-based searches
57
+ async function readBPlusTreeNode(
58
+ bbi: GenericFilehandle,
59
+ nodeOffset: number,
60
+ blockSize: number,
61
+ keySize: number,
62
+ valSize: number,
63
+ name: string,
64
+ field: number,
65
+ opts: RequestOptions,
66
+ ): Promise<Loc | undefined> {
67
+ const len = 4 + blockSize * (keySize + valSize)
68
+ const buffer = await bbi.read(len, nodeOffset, opts)
69
+ const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length)
70
+ let offset = 0
71
+ const nodeType = dataView.getInt8(offset)
72
+ offset += 2 // skip nodeType byte + 1 reserved byte
73
+ const cnt = dataView.getInt16(offset, true)
74
+ offset += 2
75
+
76
+ // Non-leaf node (nodeType === 0): contains keys and child node pointers for navigation
77
+ if (nodeType === 0) {
78
+ const leafkeys = []
79
+ for (let i = 0; i < cnt; i++) {
80
+ const key = parseKey(buffer, offset, keySize)
81
+ offset += keySize
82
+ const dataOffset = Number(dataView.getBigUint64(offset, true))
83
+ offset += 8
84
+ leafkeys.push({
85
+ key,
86
+ offset: dataOffset,
87
+ })
88
+ }
89
+
90
+ // Binary search to find the appropriate child node
91
+ let left = 0
92
+ let right = leafkeys.length - 1
93
+ let targetIndex = leafkeys.length - 1
94
+
95
+ while (left <= right) {
96
+ const mid = Math.floor((left + right) / 2)
97
+ const cmp = name.localeCompare(leafkeys[mid]!.key)
98
+
99
+ if (cmp < 0) {
100
+ targetIndex = mid - 1
101
+ right = mid - 1
102
+ } else {
103
+ left = mid + 1
104
+ }
105
+ }
106
+
107
+ const childOffset =
108
+ targetIndex >= 0 ? leafkeys[targetIndex]!.offset : leafkeys[0]!.offset
109
+ return readBPlusTreeNode(
110
+ bbi,
111
+ childOffset,
112
+ blockSize,
113
+ keySize,
114
+ valSize,
115
+ name,
116
+ field,
117
+ opts,
118
+ )
119
+ } else if (nodeType === 1) {
120
+ // Leaf node (nodeType === 1): contains actual key-value data
121
+ const keys = []
122
+ for (let i = 0; i < cnt; i++) {
123
+ const key = parseKey(buffer, offset, keySize)
124
+ offset += keySize
125
+ const dataOffset = Number(dataView.getBigUint64(offset, true))
126
+ offset += 8
127
+ const length = dataView.getUint32(offset, true)
128
+ offset += 4
129
+ offset += 4 // skip reserved
130
+ keys.push({
131
+ key,
132
+ offset: dataOffset,
133
+ length,
134
+ })
135
+ }
136
+
137
+ // Binary search for exact key match in sorted leaf node
138
+ let left = 0
139
+ let right = keys.length - 1
140
+
141
+ while (left <= right) {
142
+ const mid = Math.floor((left + right) / 2)
143
+ const cmp = name.localeCompare(keys[mid]!.key)
144
+
145
+ if (cmp === 0) {
146
+ return { ...keys[mid]!, field }
147
+ } else if (cmp < 0) {
148
+ right = mid - 1
149
+ } else {
150
+ left = mid + 1
151
+ }
152
+ }
153
+
154
+ return undefined
155
+ }
156
+ }
157
+
28
158
  export class BigBed extends BBI {
29
159
  public readIndicesCache = new AbortablePromiseCache<RequestOptions, Index[]>({
30
160
  cache: new QuickLRU({ maxSize: 1 }),
@@ -53,7 +183,7 @@ export class BigBed extends BBI {
53
183
  */
54
184
  private async _readIndices(opts: RequestOptions) {
55
185
  const { extHeaderOffset } = await this.getHeader(opts)
56
- const b = await this.bbi.read(64, Number(extHeaderOffset))
186
+ const b = await this.bbi.read(64, extHeaderOffset)
57
187
 
58
188
  const dataView = new DataView(b.buffer, b.byteOffset, b.length)
59
189
  let offset = 0
@@ -71,9 +201,9 @@ export class BigBed extends BBI {
71
201
 
72
202
  const blocklen = 20
73
203
  const len = blocklen * count
74
- const buffer = await this.bbi.read(len, Number(dataOffset))
204
+ const buffer = await this.bbi.read(len, dataOffset)
75
205
 
76
- const indices = [] as Index[]
206
+ const indices: Index[] = []
77
207
 
78
208
  for (let i = 0; i < count; i += 1) {
79
209
  const b = buffer.subarray(i * blocklen)
@@ -84,9 +214,14 @@ export class BigBed extends BBI {
84
214
  const fieldcount = dataView.getInt16(offset, true)
85
215
  offset += 2
86
216
  const dataOffset = Number(dataView.getBigUint64(offset, true))
87
- offset += 8 + 4 //4 skip
217
+ offset += 8 + 4 // skip 8-byte offset + 4 reserved bytes
88
218
  const field = dataView.getInt16(offset, true)
89
- indices.push({ type, fieldcount, offset: Number(dataOffset), field })
219
+ indices.push({
220
+ type,
221
+ fieldcount,
222
+ offset: dataOffset,
223
+ field,
224
+ })
90
225
  }
91
226
  return indices
92
227
  }
@@ -109,7 +244,6 @@ export class BigBed extends BBI {
109
244
  if (indices.length === 0) {
110
245
  return []
111
246
  }
112
- const decoder = new TextDecoder('utf8')
113
247
  const locs = indices.map(async index => {
114
248
  const { offset: offset2, field } = index
115
249
  const b = await this.bbi.read(32, offset2, opts)
@@ -127,74 +261,16 @@ export class BigBed extends BBI {
127
261
  // const _itemCount = Number(dataView.getBigUint64(offset, true))
128
262
  offset += 8
129
263
 
130
- const bptReadNode = async (nodeOffset: number) => {
131
- const val = Number(nodeOffset)
132
- const len = 4 + blockSize * (keySize + valSize)
133
- const buffer = await this.bbi.read(len, val, opts)
134
- const b = buffer
135
- const dataView = new DataView(b.buffer, b.byteOffset, b.length)
136
- let offset = 0
137
- const nodeType = dataView.getInt8(offset)
138
- offset += 2 //skip 1
139
- const cnt = dataView.getInt16(offset, true)
140
- offset += 2
141
- const keys = []
142
- if (nodeType === 0) {
143
- const leafkeys = []
144
- for (let i = 0; i < cnt; i++) {
145
- const key = decoder
146
- .decode(b.subarray(offset, offset + keySize))
147
- .replaceAll('\0', '')
148
- offset += keySize
149
- const dataOffset = Number(dataView.getBigUint64(offset, true))
150
- offset += 8
151
- leafkeys.push({
152
- key,
153
- offset: dataOffset,
154
- })
155
- }
156
-
157
- let lastOffset = 0
158
- for (const { key, offset } of leafkeys) {
159
- if (name.localeCompare(key) < 0 && lastOffset) {
160
- return bptReadNode(lastOffset)
161
- }
162
- lastOffset = offset
163
- }
164
- return bptReadNode(lastOffset)
165
- } else if (nodeType === 1) {
166
- for (let i = 0; i < cnt; i++) {
167
- const key = decoder
168
- .decode(b.subarray(offset, offset + keySize))
169
- .replaceAll('\0', '')
170
- offset += keySize
171
- const dataOffset = Number(dataView.getBigUint64(offset, true))
172
- offset += 8
173
- const length = dataView.getUint32(offset, true)
174
- offset += 4
175
- const reserved = dataView.getUint32(offset, true)
176
- offset += 4
177
- keys.push({
178
- key,
179
- offset: dataOffset,
180
- length,
181
- reserved,
182
- })
183
- }
184
-
185
- for (const n of keys) {
186
- if (n.key === name) {
187
- return {
188
- ...n,
189
- field,
190
- }
191
- }
192
- }
193
-
194
- return undefined
195
- }
196
- }
197
- return bptReadNode(offset2 + 32)
264
+ return readBPlusTreeNode(
265
+ this.bbi,
266
+ offset2 + 32,
267
+ blockSize,
268
+ keySize,
269
+ valSize,
270
+ name,
271
+ field,
272
+ opts,
273
+ )
198
274
  })
199
275
  return filterUndef(await Promise.all(locs))
200
276
  }
@@ -206,7 +282,7 @@ export class BigBed extends BBI {
206
282
  *
207
283
  * @param name - the name to search for
208
284
  *
209
- * @param opts - options object with optional AboutSignal
285
+ * @param opts - options object with optional AbortSignal
210
286
  *
211
287
  * @return array of Feature
212
288
  */
@@ -222,16 +298,22 @@ export class BigBed extends BBI {
222
298
  observer.error(e)
223
299
  })
224
300
  }).pipe(
225
- reduce((acc, curr) => acc.concat(curr)),
226
- map(x => {
227
- for (const element of x) {
228
- element.field = block.field
229
- }
230
- return x
231
- }),
301
+ reduce((acc, curr) => {
302
+ acc.push(...curr)
303
+ return acc
304
+ }, [] as Feature[]),
305
+ map(features => features.map(f => ({ ...f, field: block.field }))),
232
306
  )
233
307
  })
234
308
  const ret = await firstValueFrom(merge(...res))
235
- return ret.filter(f => f.rest?.split('\t')[(f.field || 0) - 3] === name)
309
+ // Filter to features where the indexed field matches the search name
310
+ // field offset is adjusted by -3 to account for chrom, chromStart, chromEnd columns
311
+ return ret.filter(f => {
312
+ if (!f.rest) {
313
+ return false
314
+ }
315
+ const fieldIndex = (f.field || 0) - 3
316
+ return getTabField(f.rest, fieldIndex) === name
317
+ })
236
318
  }
237
319
  }
package/src/bigwig.ts CHANGED
@@ -20,13 +20,12 @@ export class BigWig extends BBI {
20
20
 
21
21
  for (let i = maxLevel; i >= 0; i -= 1) {
22
22
  const zh = zoomLevels[i]
23
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
24
23
  if (zh && zh.reductionLevel <= 2 * basesPerPx) {
25
24
  return new BlockView(
26
25
  this.bbi,
27
26
  refsByName,
28
27
  zh.indexOffset,
29
- uncompressBufSize > 0,
28
+ uncompressBufSize,
30
29
  'summary',
31
30
  )
32
31
  }