@gmod/bbi 7.1.0 → 8.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 (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 +80 -17
  5. package/dist/bbi.js.map +1 -1
  6. package/dist/bigbed.d.ts +14 -2
  7. package/dist/bigbed.js +116 -76
  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 +324 -148
  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 +11 -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 +6 -5
  28. package/dist/util.js.map +1 -1
  29. package/dist/wasm/inflate-wasm-inlined.d.ts +18 -0
  30. package/dist/wasm/inflate-wasm-inlined.js +455 -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 +80 -17
  40. package/esm/bbi.js.map +1 -1
  41. package/esm/bigbed.d.ts +14 -2
  42. package/esm/bigbed.js +116 -76
  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 +326 -150
  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 +11 -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 +6 -5
  63. package/esm/util.js.map +1 -1
  64. package/esm/wasm/inflate-wasm-inlined.d.ts +18 -0
  65. package/esm/wasm/inflate-wasm-inlined.js +449 -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 +17 -12
  74. package/src/bbi.ts +102 -20
  75. package/src/bigbed.ts +157 -80
  76. package/src/bigwig.ts +1 -2
  77. package/src/block-view.ts +418 -156
  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 +86 -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 +547 -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/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
+ const effectiveKeyEnd =
51
+ keyEnd !== -1 && keyEnd < offset + keySize ? keyEnd : offset + keySize
52
+ return decoder.decode(buffer.subarray(offset, effectiveKeyEnd))
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 }),
@@ -73,7 +203,7 @@ export class BigBed extends BBI {
73
203
  const len = blocklen * count
74
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,7 +214,7 @@ 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
219
  indices.push({
90
220
  type,
@@ -114,7 +244,6 @@ export class BigBed extends BBI {
114
244
  if (indices.length === 0) {
115
245
  return []
116
246
  }
117
- const decoder = new TextDecoder('utf8')
118
247
  const locs = indices.map(async index => {
119
248
  const { offset: offset2, field } = index
120
249
  const b = await this.bbi.read(32, offset2, opts)
@@ -132,74 +261,16 @@ export class BigBed extends BBI {
132
261
  // const _itemCount = Number(dataView.getBigUint64(offset, true))
133
262
  offset += 8
134
263
 
135
- const bptReadNode = async (nodeOffset: number) => {
136
- const val = nodeOffset
137
- const len = 4 + blockSize * (keySize + valSize)
138
- const buffer = await this.bbi.read(len, val, opts)
139
- const b = buffer
140
- const dataView = new DataView(b.buffer, b.byteOffset, b.length)
141
- let offset = 0
142
- const nodeType = dataView.getInt8(offset)
143
- offset += 2 //skip 1
144
- const cnt = dataView.getInt16(offset, true)
145
- offset += 2
146
- const keys = []
147
- if (nodeType === 0) {
148
- const leafkeys = []
149
- for (let i = 0; i < cnt; i++) {
150
- const key = decoder
151
- .decode(b.subarray(offset, offset + keySize))
152
- .replaceAll('\0', '')
153
- offset += keySize
154
- const dataOffset = Number(dataView.getBigUint64(offset, true))
155
- offset += 8
156
- leafkeys.push({
157
- key,
158
- offset: dataOffset,
159
- })
160
- }
161
-
162
- let lastOffset = 0
163
- for (const { key, offset } of leafkeys) {
164
- if (name.localeCompare(key) < 0 && lastOffset) {
165
- return bptReadNode(lastOffset)
166
- }
167
- lastOffset = offset
168
- }
169
- return bptReadNode(lastOffset)
170
- } else if (nodeType === 1) {
171
- for (let i = 0; i < cnt; i++) {
172
- const key = decoder
173
- .decode(b.subarray(offset, offset + keySize))
174
- .replaceAll('\0', '')
175
- offset += keySize
176
- const dataOffset = Number(dataView.getBigUint64(offset, true))
177
- offset += 8
178
- const length = dataView.getUint32(offset, true)
179
- offset += 4
180
- const reserved = dataView.getUint32(offset, true)
181
- offset += 4
182
- keys.push({
183
- key,
184
- offset: dataOffset,
185
- length,
186
- reserved,
187
- })
188
- }
189
-
190
- for (const n of keys) {
191
- if (n.key === name) {
192
- return {
193
- ...n,
194
- field,
195
- }
196
- }
197
- }
198
-
199
- return undefined
200
- }
201
- }
202
- 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
+ )
203
274
  })
204
275
  return filterUndef(await Promise.all(locs))
205
276
  }
@@ -211,7 +282,7 @@ export class BigBed extends BBI {
211
282
  *
212
283
  * @param name - the name to search for
213
284
  *
214
- * @param opts - options object with optional AboutSignal
285
+ * @param opts - options object with optional AbortSignal
215
286
  *
216
287
  * @return array of Feature
217
288
  */
@@ -227,16 +298,22 @@ export class BigBed extends BBI {
227
298
  observer.error(e)
228
299
  })
229
300
  }).pipe(
230
- reduce((acc, curr) => acc.concat(curr)),
231
- map(x => {
232
- for (const element of x) {
233
- element.field = block.field
234
- }
235
- return x
236
- }),
301
+ reduce((acc, curr) => {
302
+ acc.push(...curr)
303
+ return acc
304
+ }, [] as Feature[]),
305
+ map(features => features.map(f => ({ ...f, field: block.field }))),
237
306
  )
238
307
  })
239
308
  const ret = await firstValueFrom(merge(...res))
240
- 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
+ })
241
318
  }
242
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
  }