@gmod/tabix 3.1.0 → 3.1.2
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.
- package/CHANGELOG.md +7 -1
- package/dist/csi.d.ts +0 -4
- package/dist/csi.js +1 -23
- package/dist/csi.js.map +1 -1
- package/dist/indexFile.d.ts +4 -0
- package/dist/indexFile.js +22 -0
- package/dist/indexFile.js.map +1 -1
- package/dist/package.json +1 -0
- package/dist/tabix-bundle.js +1 -0
- package/dist/tabixIndexedFile.d.ts +1 -0
- package/dist/tabixIndexedFile.js +68 -62
- package/dist/tabixIndexedFile.js.map +1 -1
- package/dist/tbi.d.ts +0 -4
- package/dist/tbi.js +1 -23
- package/dist/tbi.js.map +1 -1
- package/esm/csi.d.ts +0 -4
- package/esm/csi.js +1 -23
- package/esm/csi.js.map +1 -1
- package/esm/indexFile.d.ts +4 -0
- package/esm/indexFile.js +22 -0
- package/esm/indexFile.js.map +1 -1
- package/esm/tabixIndexedFile.d.ts +1 -0
- package/esm/tabixIndexedFile.js +68 -62
- package/esm/tabixIndexedFile.js.map +1 -1
- package/esm/tbi.d.ts +0 -4
- package/esm/tbi.js +1 -23
- package/esm/tbi.js.map +1 -1
- package/package.json +9 -9
- package/src/csi.ts +1 -26
- package/src/indexFile.ts +25 -0
- package/src/tabixIndexedFile.ts +107 -69
- package/src/tbi.ts +1 -26
package/src/csi.ts
CHANGED
|
@@ -58,7 +58,7 @@ export default class CSI extends IndexFile {
|
|
|
58
58
|
const formatFlags = dataView.getInt32(offset, true)
|
|
59
59
|
const coordinateType =
|
|
60
60
|
formatFlags & 0x10000 ? 'zero-based-half-open' : '1-based-closed'
|
|
61
|
-
const format = formats[(formatFlags &
|
|
61
|
+
const format = formats[(formatFlags & 0xf) as 0 | 1 | 2]
|
|
62
62
|
if (!format) {
|
|
63
63
|
throw new Error(`invalid Tabix preset format flags ${formatFlags}`)
|
|
64
64
|
}
|
|
@@ -87,31 +87,6 @@ export default class CSI extends IndexFile {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
_parseNameBytes(namesBytes: Uint8Array) {
|
|
91
|
-
let currRefId = 0
|
|
92
|
-
let currNameStart = 0
|
|
93
|
-
const refIdToName = []
|
|
94
|
-
const refNameToId: Record<string, number> = {}
|
|
95
|
-
const decoder = new TextDecoder('utf8')
|
|
96
|
-
for (let i = 0; i < namesBytes.length; i += 1) {
|
|
97
|
-
if (!namesBytes[i]) {
|
|
98
|
-
if (currNameStart < i) {
|
|
99
|
-
const refName = this.renameRefSeq(
|
|
100
|
-
decoder.decode(namesBytes.subarray(currNameStart, i)),
|
|
101
|
-
)
|
|
102
|
-
refIdToName[currRefId] = refName
|
|
103
|
-
refNameToId[refName] = currRefId
|
|
104
|
-
}
|
|
105
|
-
currNameStart = i + 1
|
|
106
|
-
currRefId += 1
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
refNameToId,
|
|
111
|
-
refIdToName,
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
90
|
async _parse(opts: Options = {}) {
|
|
116
91
|
const bytes = await unzip(await this.filehandle.readFile(opts))
|
|
117
92
|
const dataView = new DataView(bytes.buffer)
|
package/src/indexFile.ts
CHANGED
|
@@ -76,4 +76,29 @@ export default abstract class IndexFile {
|
|
|
76
76
|
const idx = await this.parse(opts)
|
|
77
77
|
return !!idx.indices[seqId]?.binIndex
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
_parseNameBytes(namesBytes: Uint8Array) {
|
|
81
|
+
let currRefId = 0
|
|
82
|
+
let currNameStart = 0
|
|
83
|
+
const refIdToName: string[] = []
|
|
84
|
+
const refNameToId: Record<string, number> = {}
|
|
85
|
+
const decoder = new TextDecoder('utf8')
|
|
86
|
+
for (let i = 0; i < namesBytes.length; i += 1) {
|
|
87
|
+
if (!namesBytes[i]) {
|
|
88
|
+
if (currNameStart < i) {
|
|
89
|
+
const refName = this.renameRefSeq(
|
|
90
|
+
decoder.decode(namesBytes.subarray(currNameStart, i)),
|
|
91
|
+
)
|
|
92
|
+
refIdToName[currRefId] = refName
|
|
93
|
+
refNameToId[refName] = currRefId
|
|
94
|
+
}
|
|
95
|
+
currNameStart = i + 1
|
|
96
|
+
currRefId += 1
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
refNameToId,
|
|
101
|
+
refIdToName,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
79
104
|
}
|
package/src/tabixIndexedFile.ts
CHANGED
|
@@ -11,11 +11,6 @@ import { checkAbortSignal } from './util.ts'
|
|
|
11
11
|
|
|
12
12
|
import type { GenericFilehandle } from 'generic-filehandle2'
|
|
13
13
|
|
|
14
|
-
function isASCII(str: string) {
|
|
15
|
-
// eslint-disable-next-line no-control-regex
|
|
16
|
-
return /^[\u0000-\u007F]*$/.test(str)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
type GetLinesCallback = (line: string, fileOffset: number) => void
|
|
20
15
|
|
|
21
16
|
interface GetLinesOpts {
|
|
@@ -164,6 +159,21 @@ export default class TabixIndexedFile {
|
|
|
164
159
|
* @returns promise that is resolved when the whole read is finished,
|
|
165
160
|
* rejected on error
|
|
166
161
|
*/
|
|
162
|
+
private calculateFileOffset(
|
|
163
|
+
cpositions: number[],
|
|
164
|
+
dpositions: number[],
|
|
165
|
+
pos: number,
|
|
166
|
+
blockStart: number,
|
|
167
|
+
minvDataPosition: number,
|
|
168
|
+
) {
|
|
169
|
+
return (
|
|
170
|
+
cpositions[pos]! * (1 << 8) +
|
|
171
|
+
(blockStart - dpositions[pos]!) +
|
|
172
|
+
minvDataPosition +
|
|
173
|
+
1
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
167
177
|
async getLines(
|
|
168
178
|
refName: string,
|
|
169
179
|
s: number | undefined,
|
|
@@ -212,69 +222,104 @@ export default class TabixIndexedFile {
|
|
|
212
222
|
let pos = 0
|
|
213
223
|
|
|
214
224
|
// fast path, Buffer is just ASCII chars and not gigantor, can be
|
|
215
|
-
// converted to string and processed directly.
|
|
216
|
-
//
|
|
225
|
+
// converted to string and processed directly.
|
|
226
|
+
//
|
|
227
|
+
// if it is not ASCII or, we have to decode line by line, as it is
|
|
228
|
+
// otherwise hard to get the right 'fileOffset' based feature IDs
|
|
229
|
+
//
|
|
230
|
+
// we use a basic check for isASCII: string length equals buffer length
|
|
231
|
+
// if it is ASCII...no multi-byte decodings
|
|
217
232
|
const str = decoder.decode(buffer)
|
|
218
|
-
const strIsASCII =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (strIsASCII) {
|
|
223
|
-
n = str.indexOf('\n', blockStart)
|
|
233
|
+
const strIsASCII = buffer.length == str.length
|
|
234
|
+
if (strIsASCII) {
|
|
235
|
+
while (blockStart < str.length) {
|
|
236
|
+
const n = str.indexOf('\n', blockStart)
|
|
224
237
|
if (n === -1) {
|
|
225
238
|
break
|
|
226
239
|
}
|
|
227
|
-
line = str.slice(blockStart, n)
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
const line = str.slice(blockStart, n)
|
|
241
|
+
|
|
242
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
243
|
+
if (dpositions) {
|
|
244
|
+
const target = blockStart + c.minv.dataPosition
|
|
245
|
+
while (pos < dpositions.length && target >= dpositions[pos]!) {
|
|
246
|
+
pos++
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// filter the line for whether it is within the requested range
|
|
251
|
+
const { startCoordinate, overlaps } = this.checkLine(
|
|
252
|
+
metadata,
|
|
253
|
+
refName,
|
|
254
|
+
start,
|
|
255
|
+
end,
|
|
256
|
+
line,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if (overlaps) {
|
|
260
|
+
callback(
|
|
261
|
+
line,
|
|
262
|
+
this.calculateFileOffset(
|
|
263
|
+
cpositions,
|
|
264
|
+
dpositions,
|
|
265
|
+
pos,
|
|
266
|
+
blockStart,
|
|
267
|
+
c.minv.dataPosition,
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
} else if (startCoordinate !== undefined && startCoordinate >= end) {
|
|
271
|
+
// the lines were overlapping the region, but now have stopped, so we
|
|
272
|
+
// must be at the end of the relevant data and we can stop processing
|
|
273
|
+
// data now
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
blockStart = n + 1
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
while (blockStart < buffer.length) {
|
|
280
|
+
const n = buffer.indexOf('\n'.charCodeAt(0), blockStart)
|
|
230
281
|
if (n === -1) {
|
|
231
282
|
break
|
|
232
283
|
}
|
|
233
284
|
const b = buffer.slice(blockStart, n)
|
|
234
|
-
line = decoder.decode(b)
|
|
235
|
-
}
|
|
285
|
+
const line = decoder.decode(b)
|
|
236
286
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
287
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
288
|
+
if (dpositions) {
|
|
289
|
+
const target = blockStart + c.minv.dataPosition
|
|
290
|
+
while (pos < dpositions.length && target >= dpositions[pos]!) {
|
|
291
|
+
pos++
|
|
292
|
+
}
|
|
293
|
+
}
|
|
242
294
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
line,
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
if (overlaps) {
|
|
253
|
-
callback(
|
|
295
|
+
// filter the line for whether it is within the requested range
|
|
296
|
+
const { startCoordinate, overlaps } = this.checkLine(
|
|
297
|
+
metadata,
|
|
298
|
+
refName,
|
|
299
|
+
start,
|
|
300
|
+
end,
|
|
254
301
|
line,
|
|
255
|
-
// cpositions[pos] refers to actual file offset of a bgzip block
|
|
256
|
-
// boundaries
|
|
257
|
-
//
|
|
258
|
-
// we multiply by (1 <<8) in order to make sure each block has a
|
|
259
|
-
// "unique" address space so that data in that block could never
|
|
260
|
-
// overlap
|
|
261
|
-
//
|
|
262
|
-
// then the blockStart-dpositions is an uncompressed file offset
|
|
263
|
-
// from that bgzip block boundary, and since the cpositions are
|
|
264
|
-
// multiplied by (1 << 8) these uncompressed offsets get a unique
|
|
265
|
-
// space
|
|
266
|
-
cpositions[pos]! * (1 << 8) +
|
|
267
|
-
(blockStart - dpositions[pos]!) +
|
|
268
|
-
c.minv.dataPosition +
|
|
269
|
-
1,
|
|
270
302
|
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
303
|
+
|
|
304
|
+
if (overlaps) {
|
|
305
|
+
callback(
|
|
306
|
+
line,
|
|
307
|
+
this.calculateFileOffset(
|
|
308
|
+
cpositions,
|
|
309
|
+
dpositions,
|
|
310
|
+
pos,
|
|
311
|
+
blockStart,
|
|
312
|
+
c.minv.dataPosition,
|
|
313
|
+
),
|
|
314
|
+
)
|
|
315
|
+
} else if (startCoordinate !== undefined && startCoordinate >= end) {
|
|
316
|
+
// the lines were overlapping the region, but now have stopped, so we
|
|
317
|
+
// must be at the end of the relevant data and we can stop processing
|
|
318
|
+
// data now
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
blockStart = n + 1
|
|
276
322
|
}
|
|
277
|
-
blockStart = n + 1
|
|
278
323
|
}
|
|
279
324
|
}
|
|
280
325
|
}
|
|
@@ -448,11 +493,11 @@ export default class TabixIndexedFile {
|
|
|
448
493
|
}
|
|
449
494
|
}
|
|
450
495
|
}
|
|
451
|
-
|
|
452
|
-
currentColumnNumber += 1
|
|
453
|
-
if (currentColumnNumber > maxColumn) {
|
|
496
|
+
if (currentColumnNumber === maxColumn) {
|
|
454
497
|
break
|
|
455
498
|
}
|
|
499
|
+
currentColumnStart = i + 1
|
|
500
|
+
currentColumnNumber += 1
|
|
456
501
|
}
|
|
457
502
|
}
|
|
458
503
|
return {
|
|
@@ -470,17 +515,10 @@ export default class TabixIndexedFile {
|
|
|
470
515
|
// be another pairwise feature at the end of this one
|
|
471
516
|
const isTRA = info.includes('SVTYPE=TRA')
|
|
472
517
|
if (info[0] !== '.' && !isTRA) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (valueEnd === -1) {
|
|
478
|
-
valueEnd = info.length
|
|
479
|
-
}
|
|
480
|
-
endCoordinate = Number.parseInt(info.slice(j + 4, valueEnd), 10)
|
|
481
|
-
break
|
|
482
|
-
}
|
|
483
|
-
prevChar = info[j]
|
|
518
|
+
const endRegex = /(?:^|;)END=([^;]+)/
|
|
519
|
+
const match = endRegex.exec(info)
|
|
520
|
+
if (match) {
|
|
521
|
+
endCoordinate = Number.parseInt(match[1]!, 10)
|
|
484
522
|
}
|
|
485
523
|
} else if (isTRA) {
|
|
486
524
|
return startCoordinate + 1
|
package/src/tbi.ts
CHANGED
|
@@ -62,7 +62,7 @@ export default class TabixIndex extends IndexFile {
|
|
|
62
62
|
1: 'SAM',
|
|
63
63
|
2: 'VCF',
|
|
64
64
|
}
|
|
65
|
-
const format = formatOpts[formatFlags &
|
|
65
|
+
const format = formatOpts[formatFlags & 0xf]
|
|
66
66
|
if (!format) {
|
|
67
67
|
throw new Error(`invalid Tabix preset format flags ${formatFlags}`)
|
|
68
68
|
}
|
|
@@ -160,31 +160,6 @@ export default class TabixIndex extends IndexFile {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
_parseNameBytes(namesBytes: Uint8Array) {
|
|
164
|
-
let currRefId = 0
|
|
165
|
-
let currNameStart = 0
|
|
166
|
-
const refIdToName: string[] = []
|
|
167
|
-
const refNameToId: Record<string, number> = {}
|
|
168
|
-
const decoder = new TextDecoder('utf8')
|
|
169
|
-
for (let i = 0; i < namesBytes.length; i += 1) {
|
|
170
|
-
if (!namesBytes[i]) {
|
|
171
|
-
if (currNameStart < i) {
|
|
172
|
-
const refName = this.renameRefSeq(
|
|
173
|
-
decoder.decode(namesBytes.subarray(currNameStart, i)),
|
|
174
|
-
)
|
|
175
|
-
refIdToName[currRefId] = refName
|
|
176
|
-
refNameToId[refName] = currRefId
|
|
177
|
-
}
|
|
178
|
-
currNameStart = i + 1
|
|
179
|
-
currRefId += 1
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return {
|
|
183
|
-
refNameToId,
|
|
184
|
-
refIdToName,
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
163
|
async blocksForRange(
|
|
189
164
|
refName: string,
|
|
190
165
|
min: number,
|