@gmod/bbi 1.0.34 → 2.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 (60) hide show
  1. package/CHANGELOG.md +17 -8
  2. package/dist/bbi.d.ts +2 -2
  3. package/dist/bbi.js +56 -59
  4. package/dist/bbi.js.map +1 -0
  5. package/dist/bigbed.d.ts +1 -2
  6. package/dist/bigbed.js +23 -20
  7. package/dist/bigbed.js.map +1 -0
  8. package/dist/bigwig.d.ts +1 -3
  9. package/dist/bigwig.js +5 -8
  10. package/dist/bigwig.js.map +1 -0
  11. package/dist/blockView.d.ts +8 -9
  12. package/dist/blockView.js +156 -95
  13. package/dist/blockView.js.map +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/range.js +2 -0
  17. package/dist/range.js.map +1 -0
  18. package/dist/unzip-pako.d.ts +1 -1
  19. package/dist/unzip-pako.js +2 -1
  20. package/dist/unzip-pako.js.map +1 -0
  21. package/dist/unzip.js +1 -0
  22. package/dist/unzip.js.map +1 -0
  23. package/dist/util.d.ts +11 -1
  24. package/dist/util.js +10 -4
  25. package/dist/util.js.map +1 -0
  26. package/esm/bbi.d.ts +2 -2
  27. package/esm/bbi.js +62 -67
  28. package/esm/bbi.js.map +1 -0
  29. package/esm/bigbed.d.ts +1 -2
  30. package/esm/bigbed.js +42 -46
  31. package/esm/bigbed.js.map +1 -0
  32. package/esm/bigwig.d.ts +1 -3
  33. package/esm/bigwig.js +7 -14
  34. package/esm/bigwig.js.map +1 -0
  35. package/esm/blockView.d.ts +8 -9
  36. package/esm/blockView.js +168 -118
  37. package/esm/blockView.js.map +1 -0
  38. package/esm/index.js +3 -7
  39. package/esm/index.js.map +1 -0
  40. package/esm/range.js +3 -4
  41. package/esm/range.js.map +1 -0
  42. package/esm/unzip-pako.d.ts +1 -1
  43. package/esm/unzip-pako.js +4 -7
  44. package/esm/unzip-pako.js.map +1 -0
  45. package/esm/unzip.js +3 -5
  46. package/esm/unzip.js.map +1 -0
  47. package/esm/util.d.ts +11 -1
  48. package/esm/util.js +14 -15
  49. package/esm/util.js.map +1 -0
  50. package/package.json +14 -14
  51. package/src/bbi.ts +375 -0
  52. package/src/bigbed.ts +244 -0
  53. package/src/bigwig.ts +38 -0
  54. package/src/blockView.ts +496 -0
  55. package/src/declare.d.ts +2 -0
  56. package/src/index.ts +3 -0
  57. package/src/range.ts +142 -0
  58. package/src/unzip-pako.ts +5 -0
  59. package/src/unzip.ts +2 -0
  60. package/src/util.ts +83 -0
package/esm/util.js CHANGED
@@ -1,24 +1,25 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.abortBreakPoint = exports.checkAbortSignal = exports.groupBlocks = exports.AbortError = void 0;
4
1
  /* eslint no-bitwise: ["error", { "allow": ["|"] }] */
5
- class AbortError extends Error {
2
+ export class AbortError extends Error {
6
3
  constructor(message) {
7
4
  super(message);
8
5
  this.code = 'ERR_ABORTED';
9
6
  }
10
7
  }
11
- exports.AbortError = AbortError;
12
8
  // sort blocks by file offset and
13
9
  // group blocks that are within 2KB of eachother
14
- function groupBlocks(blocks) {
15
- blocks.sort((b0, b1) => (b0.offset | 0) - (b1.offset | 0));
10
+ export function groupBlocks(blocks) {
11
+ blocks.sort((b0, b1) => Number(b0.offset) - Number(b1.offset));
16
12
  const blockGroups = [];
17
13
  let lastBlock;
18
14
  let lastBlockEnd;
19
15
  for (let i = 0; i < blocks.length; i += 1) {
20
- if (lastBlock && blocks[i].offset - lastBlockEnd <= 2000) {
21
- lastBlock.length += blocks[i].length - lastBlockEnd + blocks[i].offset;
16
+ if (lastBlock &&
17
+ lastBlockEnd &&
18
+ Number(blocks[i].offset) - lastBlockEnd <= 2000) {
19
+ lastBlock.length = BigInt(Number(lastBlock.length) +
20
+ Number(blocks[i].length) -
21
+ lastBlockEnd +
22
+ Number(blocks[i].offset));
22
23
  lastBlock.blocks.push(blocks[i]);
23
24
  }
24
25
  else {
@@ -28,11 +29,10 @@ function groupBlocks(blocks) {
28
29
  offset: blocks[i].offset,
29
30
  }));
30
31
  }
31
- lastBlockEnd = lastBlock.offset + lastBlock.length;
32
+ lastBlockEnd = Number(lastBlock.offset) + Number(lastBlock.length);
32
33
  }
33
34
  return blockGroups;
34
35
  }
35
- exports.groupBlocks = groupBlocks;
36
36
  /**
37
37
  * Properly check if the given AbortSignal is aborted.
38
38
  * Per the standard, if the signal reads as aborted,
@@ -44,7 +44,7 @@ exports.groupBlocks = groupBlocks;
44
44
  * @param {AbortSignal} [signal] an AbortSignal, or anything with an `aborted` attribute
45
45
  * @returns nothing
46
46
  */
47
- function checkAbortSignal(signal) {
47
+ export function checkAbortSignal(signal) {
48
48
  if (!signal) {
49
49
  return;
50
50
  }
@@ -60,15 +60,14 @@ function checkAbortSignal(signal) {
60
60
  }
61
61
  }
62
62
  }
63
- exports.checkAbortSignal = checkAbortSignal;
64
63
  /**
65
64
  * Skips to the next tick, then runs `checkAbortSignal`.
66
65
  * Await this to inside an otherwise synchronous loop to
67
66
  * provide a place to break when an abort signal is received.
68
67
  * @param {AbortSignal} signal
69
68
  */
70
- async function abortBreakPoint(signal) {
69
+ export async function abortBreakPoint(signal) {
71
70
  await Promise.resolve();
72
71
  checkAbortSignal(signal);
73
72
  }
74
- exports.abortBreakPoint = abortBreakPoint;
73
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,MAAM,OAAO,UAAW,SAAQ,KAAK;IAGnC,YAAmB,OAAe;QAChC,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;IAC3B,CAAC;CACF;AACD,iCAAiC;AACjC,gDAAgD;AAChD,MAAM,UAAU,WAAW,CAAC,MAA4C;IACtE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAE9D,MAAM,WAAW,GAAG,EAAE,CAAA;IACtB,IAAI,SAAS,CAAA;IACb,IAAI,YAAY,CAAA;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE;QACzC,IACE,SAAS;YACT,YAAY;YACZ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,YAAY,IAAI,IAAI,EAC/C;YACA,SAAS,CAAC,MAAM,GAAG,MAAM,CACvB,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;gBACtB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBACxB,YAAY;gBACZ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAC3B,CAAA;YACD,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;SACjC;aAAM;YACL,WAAW,CAAC,IAAI,CACd,CAAC,SAAS,GAAG;gBACX,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACnB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM;gBACxB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM;aACzB,CAAC,CACH,CAAA;SACF;QACD,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;KACnE;IAED,OAAO,WAAW,CAAA;AACpB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAoB;IACnD,IAAI,CAAC,MAAM,EAAE;QACX,OAAM;KACP;IAED,IAAI,MAAM,CAAC,OAAO,EAAE;QAClB,8BAA8B;QAC9B,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE;YACvC,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;SAChD;aAAM;YACL,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAA;YACnC,CAAC,CAAC,IAAI,GAAG,aAAa,CAAA;YACtB,MAAM,CAAC,CAAA;SACR;KACF;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAoB;IACxD,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IACvB,gBAAgB,CAAC,MAAM,CAAC,CAAA;AAC1B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmod/bbi",
3
- "version": "1.0.34",
3
+ "version": "2.0.1",
4
4
  "description": "Parser for BigWig/BigBed files",
5
5
  "license": "MIT",
6
6
  "repository": "GMOD/bbi-js",
@@ -16,7 +16,8 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
- "esm"
19
+ "esm",
20
+ "src"
20
21
  ],
21
22
  "scripts": {
22
23
  "test": "jest",
@@ -24,8 +25,8 @@
24
25
  "lint": "eslint --report-unused-disable-directives --max-warnings 0 --ext .js,.ts src",
25
26
  "clean": "rimraf dist esm",
26
27
  "prebuild": "npm run clean",
27
- "build:esm": "tsc --target es2018 --outDir esm",
28
- "build:es5": "tsc --target es5 --outDir dist",
28
+ "build:esm": "tsc --target es2018 --outDir esm --module es2020",
29
+ "build:es5": "tsc --target es5 --outDir dist --module commonjs",
29
30
  "build": "npm run build:esm && npm run build:es5",
30
31
  "preversion": "npm run lint && npm test && npm run build",
31
32
  "version": "standard-changelog && git add CHANGELOG.md",
@@ -40,21 +41,19 @@
40
41
  "genomics"
41
42
  ],
42
43
  "dependencies": {
43
- "@gmod/binary-parser": "^1.4.0",
44
44
  "abortable-promise-cache": "^1.4.1",
45
- "buffer-crc32": "^0.2.13",
46
- "es6-promisify": "^6.0.1",
47
- "generic-filehandle": "^2.0.0",
48
- "pako": "^1.0.0",
45
+ "binary-parser": "^2.1.0",
46
+ "generic-filehandle": "^3.0.0",
47
+ "pako": "^2.0.0",
49
48
  "quick-lru": "^4.0.0",
50
49
  "rxjs": "^6.5.2"
51
50
  },
52
51
  "devDependencies": {
53
52
  "@gmod/bed": "^2.0.0",
54
- "@types/jest": "^27.0.3",
53
+ "@types/jest": "^28.1.4",
55
54
  "@types/long": "^4.0.0",
56
- "@types/node": "^12.0.2",
57
- "@types/pako": "^1.0.3",
55
+ "@types/node": "^18.0.0",
56
+ "@types/pako": "^2.0.0",
58
57
  "@typescript-eslint/eslint-plugin": "^5.10.0",
59
58
  "@typescript-eslint/parser": "^5.10.0",
60
59
  "cross-fetch": "^3.0.2",
@@ -62,11 +61,12 @@
62
61
  "eslint-config-prettier": "^8.3.0",
63
62
  "eslint-plugin-import": "^2.25.3",
64
63
  "eslint-plugin-prettier": "^4.0.0",
65
- "jest": "^27.4.3",
64
+ "jest": "^28.1.2",
65
+ "jest-environment-jsdom": "^28.1.2",
66
66
  "prettier": "^2.5.1",
67
67
  "rimraf": "^3.0.2",
68
68
  "standard-changelog": "^2.0.11",
69
- "ts-jest": "^27.0.7",
69
+ "ts-jest": "^28.0.5",
70
70
  "typescript": "^4.5.2"
71
71
  },
72
72
  "publishConfig": {
package/src/bbi.ts ADDED
@@ -0,0 +1,375 @@
1
+ import { Parser } from 'binary-parser'
2
+ import { LocalFile, RemoteFile, GenericFilehandle } from 'generic-filehandle'
3
+ import { Observable, Observer } from 'rxjs'
4
+ import { reduce } from 'rxjs/operators'
5
+ import { BlockView } from './blockView'
6
+
7
+ const BIG_WIG_MAGIC = -2003829722
8
+ const BIG_BED_MAGIC = -2021002517
9
+
10
+ export interface Feature {
11
+ start: number
12
+ end: number
13
+ score: number
14
+ rest?: string // for bigbed line
15
+ minScore?: number // for summary line
16
+ maxScore?: number // for summary line
17
+ summary?: boolean // is summary line
18
+ uniqueId?: string // for bigbed contains uniqueId calculated from file offset
19
+ field?: number // used in bigbed searching
20
+ }
21
+ interface Statistics {
22
+ scoreSum: number
23
+ basesCovered: number
24
+ scoreSumSquares: number
25
+ }
26
+
27
+ interface RefInfo {
28
+ name: string
29
+ id: number
30
+ length: number
31
+ }
32
+ export interface Header {
33
+ autoSql: string
34
+ totalSummary: Statistics
35
+ zoomLevels: any
36
+ unzoomedIndexOffset: number
37
+ unzoomedDataOffset: number
38
+ definedFieldCount: number
39
+ uncompressBufSize: number
40
+ chromTreeOffset: number
41
+ fileSize: number
42
+ extHeaderOffset: number
43
+ isBigEndian: boolean
44
+ fileType: string
45
+ refsByName: { [key: string]: number }
46
+ refsByNumber: { [key: number]: RefInfo }
47
+ }
48
+
49
+ /* get the compiled parsers for different sections of the bigwig file
50
+ *
51
+ * @param isBE - is big endian, typically false
52
+ * @return an object with compiled parsers
53
+ */
54
+ function getParsers(isBE: boolean) {
55
+ const le = isBE ? 'big' : 'little'
56
+ const headerParser = new Parser()
57
+ .endianess(le)
58
+ .int32('magic')
59
+ .uint16('version')
60
+ .uint16('numZoomLevels')
61
+ .uint64('chromTreeOffset')
62
+ .uint64('unzoomedDataOffset')
63
+ .uint64('unzoomedIndexOffset')
64
+ .uint16('fieldCount')
65
+ .uint16('definedFieldCount')
66
+ .uint64('asOffset') // autoSql offset, used in bigbed
67
+ .uint64('totalSummaryOffset')
68
+ .uint32('uncompressBufSize')
69
+ .uint64('extHeaderOffset') // name index offset, used in bigbed
70
+ .array('zoomLevels', {
71
+ length: 'numZoomLevels',
72
+ type: new Parser()
73
+ .endianess(le)
74
+ .uint32('reductionLevel')
75
+ .uint32('reserved')
76
+ .uint64('dataOffset')
77
+ .uint64('indexOffset'),
78
+ })
79
+
80
+ const totalSummaryParser = new Parser()
81
+ .endianess(le)
82
+ .uint64('basesCovered')
83
+ .doublele('scoreMin')
84
+ .doublele('scoreMax')
85
+ .doublele('scoreSum')
86
+ .doublele('scoreSumSquares')
87
+
88
+ const chromTreeParser = new Parser()
89
+ .endianess(le)
90
+ .uint32('magic')
91
+ .uint32('blockSize')
92
+ .uint32('keySize')
93
+ .uint32('valSize')
94
+ .uint64('itemCount')
95
+
96
+ const isLeafNode = new Parser()
97
+ .endianess(le)
98
+ .uint8('isLeafNode')
99
+ .skip(1)
100
+ .uint16('cnt')
101
+ .saveOffset('offset')
102
+
103
+ return {
104
+ chromTreeParser,
105
+ totalSummaryParser,
106
+ headerParser,
107
+ isLeafNode,
108
+ }
109
+ }
110
+
111
+ export interface RequestOptions {
112
+ signal?: AbortSignal
113
+ headers?: Record<string, string>
114
+ [key: string]: unknown
115
+ }
116
+
117
+ export abstract class BBI {
118
+ protected bbi: GenericFilehandle
119
+
120
+ private headerP?: Promise<Header>
121
+
122
+ protected renameRefSeqs: (a: string) => string
123
+
124
+ /* fetch and parse header information from a bigwig or bigbed file
125
+ * @param abortSignal - abort the operation, can be null
126
+ * @return a Header object
127
+ */
128
+ public getHeader(opts: RequestOptions | AbortSignal = {}) {
129
+ const options = 'aborted' in opts ? { signal: opts as AbortSignal } : opts
130
+ if (!this.headerP) {
131
+ this.headerP = this._getHeader(options).catch(e => {
132
+ this.headerP = undefined
133
+ throw e
134
+ })
135
+ }
136
+ return this.headerP
137
+ }
138
+
139
+ /*
140
+ * @param filehandle - a filehandle from generic-filehandle or implementing something similar to the node10 fs.promises API
141
+ * @param path - a Local file path as a string
142
+ * @param url - a URL string
143
+ * @param renameRefSeqs - an optional method to rename the internal reference sequences using a mapping function
144
+ */
145
+ public constructor(
146
+ options: {
147
+ filehandle?: GenericFilehandle
148
+ path?: string
149
+ url?: string
150
+ renameRefSeqs?: (a: string) => string
151
+ } = {},
152
+ ) {
153
+ const { filehandle, renameRefSeqs = s => s, path, url } = options
154
+ this.renameRefSeqs = renameRefSeqs
155
+ if (filehandle) {
156
+ this.bbi = filehandle
157
+ } else if (url) {
158
+ this.bbi = new RemoteFile(url)
159
+ } else if (path) {
160
+ this.bbi = new LocalFile(path)
161
+ } else {
162
+ throw new Error('no file given')
163
+ }
164
+ }
165
+
166
+ private async _getHeader(opts: RequestOptions) {
167
+ const header = await this._getMainHeader(opts)
168
+ const chroms = await this._readChromTree(header, opts)
169
+ return { ...header, ...chroms }
170
+ }
171
+
172
+ private async _getMainHeader(
173
+ opts: RequestOptions,
174
+ requestSize = 2000,
175
+ ): Promise<Header> {
176
+ const { buffer } = await this.bbi.read(
177
+ Buffer.alloc(requestSize),
178
+ 0,
179
+ requestSize,
180
+ 0,
181
+ opts,
182
+ )
183
+ const isBigEndian = this._isBigEndian(buffer)
184
+ const ret = getParsers(isBigEndian)
185
+ const header = ret.headerParser.parse(buffer)
186
+ const { magic, asOffset, totalSummaryOffset } = header
187
+ header.fileType = magic === BIG_BED_MAGIC ? 'bigbed' : 'bigwig'
188
+ if (asOffset > requestSize || totalSummaryOffset > requestSize) {
189
+ return this._getMainHeader(opts, requestSize * 2)
190
+ }
191
+ if (asOffset) {
192
+ const off = Number(header.asOffset)
193
+ header.autoSql = buffer
194
+ .subarray(off, buffer.indexOf(0, off))
195
+ .toString('utf8')
196
+ }
197
+ if (header.totalSummaryOffset > requestSize) {
198
+ return this._getMainHeader(opts, requestSize * 2)
199
+ }
200
+ if (header.totalSummaryOffset) {
201
+ const tail = buffer.subarray(Number(header.totalSummaryOffset))
202
+ header.totalSummary = ret.totalSummaryParser.parse(tail)
203
+ }
204
+ return { ...header, isBigEndian }
205
+ }
206
+
207
+ private _isBigEndian(buffer: Buffer): boolean {
208
+ let ret = buffer.readInt32LE(0)
209
+ if (ret === BIG_WIG_MAGIC || ret === BIG_BED_MAGIC) {
210
+ return false
211
+ }
212
+ ret = buffer.readInt32BE(0)
213
+ if (ret === BIG_WIG_MAGIC || ret === BIG_BED_MAGIC) {
214
+ return true
215
+ }
216
+ throw new Error('not a BigWig/BigBed file')
217
+ }
218
+
219
+ // todo: add progress if long running
220
+ private async _readChromTree(header: Header, opts: { signal?: AbortSignal }) {
221
+ const isBE = header.isBigEndian
222
+ const le = isBE ? 'big' : 'little'
223
+ const refsByNumber: {
224
+ [key: number]: { name: string; id: number; length: number }
225
+ } = []
226
+ const refsByName: { [key: string]: number } = {}
227
+
228
+ let unzoomedDataOffset = Number(header.unzoomedDataOffset)
229
+ const chromTreeOffset = Number(header.chromTreeOffset)
230
+ while (unzoomedDataOffset % 4 !== 0) {
231
+ unzoomedDataOffset += 1
232
+ }
233
+ const off = unzoomedDataOffset - chromTreeOffset
234
+ const { buffer } = await this.bbi.read(
235
+ Buffer.alloc(off),
236
+ 0,
237
+ off,
238
+ Number(chromTreeOffset),
239
+ opts,
240
+ )
241
+
242
+ const p = getParsers(isBE)
243
+ const { keySize } = p.chromTreeParser.parse(buffer)
244
+ const leafNodeParser = new Parser()
245
+ .endianess(le)
246
+ .string('key', { stripNull: true, length: keySize })
247
+ .uint32('refId')
248
+ .uint32('refSize')
249
+ .saveOffset('offset')
250
+ const nonleafNodeParser = new Parser()
251
+ .endianess(le)
252
+ .skip(keySize)
253
+ .uint64('childOffset')
254
+ .saveOffset('offset')
255
+ const rootNodeOffset = 32
256
+ const bptReadNode = async (currentOffset: number) => {
257
+ let offset = currentOffset
258
+ if (offset >= buffer.length) {
259
+ throw new Error('reading beyond end of buffer')
260
+ }
261
+ const ret = p.isLeafNode.parse(buffer.subarray(offset))
262
+ const { isLeafNode, cnt } = ret
263
+ offset += ret.offset
264
+ if (isLeafNode) {
265
+ for (let n = 0; n < cnt; n += 1) {
266
+ const leafRet = leafNodeParser.parse(buffer.subarray(offset))
267
+ offset += leafRet.offset
268
+ const { key, refId, refSize } = leafRet
269
+ const refRec = { name: key, id: refId, length: refSize }
270
+ refsByName[this.renameRefSeqs(key)] = refId
271
+ refsByNumber[refId] = refRec
272
+ }
273
+ } else {
274
+ // parse index node
275
+ const nextNodes = []
276
+ for (let n = 0; n < cnt; n += 1) {
277
+ const nonleafRet = nonleafNodeParser.parse(buffer.subarray(offset))
278
+ const { childOffset } = nonleafRet
279
+ offset += nonleafRet.offset
280
+ nextNodes.push(
281
+ bptReadNode(Number(childOffset) - Number(chromTreeOffset)),
282
+ )
283
+ }
284
+ await Promise.all(nextNodes)
285
+ }
286
+ }
287
+ await bptReadNode(rootNodeOffset)
288
+ return {
289
+ refsByName,
290
+ refsByNumber,
291
+ }
292
+ }
293
+
294
+ /*
295
+ * fetches the "unzoomed" view of the bigwig data. this is the default for bigbed
296
+ * @param abortSignal - a signal to optionally abort this operation
297
+ */
298
+ protected async getUnzoomedView(opts: RequestOptions): Promise<BlockView> {
299
+ const {
300
+ unzoomedIndexOffset,
301
+ refsByName,
302
+ uncompressBufSize,
303
+ isBigEndian,
304
+ fileType,
305
+ } = await this.getHeader(opts)
306
+ return new BlockView(
307
+ this.bbi,
308
+ refsByName,
309
+ unzoomedIndexOffset,
310
+ isBigEndian,
311
+ uncompressBufSize > 0,
312
+ fileType,
313
+ )
314
+ }
315
+
316
+ /*
317
+ * abstract method - get the view for a given scale
318
+ */
319
+ protected abstract getView(
320
+ scale: number,
321
+ opts: RequestOptions,
322
+ ): Promise<BlockView>
323
+
324
+ /**
325
+ * Gets features from a BigWig file
326
+ *
327
+ * @param refName - The chromosome name
328
+ * @param start - The start of a region
329
+ * @param end - The end of a region
330
+ * @param opts - An object containing basesPerSpan (e.g. pixels per basepair) or scale used to infer the zoomLevel to use
331
+ */
332
+ public async getFeatureStream(
333
+ refName: string,
334
+ start: number,
335
+ end: number,
336
+ opts: RequestOptions & { scale?: number; basesPerSpan?: number } = {
337
+ scale: 1,
338
+ },
339
+ ): Promise<Observable<Feature[]>> {
340
+ await this.getHeader(opts)
341
+ const chrName = this.renameRefSeqs(refName)
342
+ let view: BlockView
343
+
344
+ if (opts.basesPerSpan) {
345
+ view = await this.getView(1 / opts.basesPerSpan, opts)
346
+ } else if (opts.scale) {
347
+ view = await this.getView(opts.scale, opts)
348
+ } else {
349
+ view = await this.getView(1, opts)
350
+ }
351
+
352
+ if (!view) {
353
+ throw new Error('unable to get block view for data')
354
+ }
355
+ return new Observable((observer: Observer<Feature[]>): void => {
356
+ view.readWigData(chrName, start, end, observer, opts)
357
+ })
358
+ }
359
+
360
+ public async getFeatures(
361
+ refName: string,
362
+ start: number,
363
+ end: number,
364
+ opts: RequestOptions & { scale?: number; basesPerSpan?: number } = {
365
+ scale: 1,
366
+ },
367
+ ): Promise<Feature[]> {
368
+ const ob = await this.getFeatureStream(refName, start, end, opts)
369
+
370
+ const ret = await ob
371
+ .pipe(reduce((acc, curr) => acc.concat(curr)))
372
+ .toPromise()
373
+ return ret || []
374
+ }
375
+ }