@gmod/bam 7.1.18 → 7.1.20

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 (47) hide show
  1. package/README.md +5 -4
  2. package/dist/bai.js +1 -0
  3. package/dist/bai.js.map +1 -1
  4. package/dist/bamFile.d.ts +1 -0
  5. package/dist/bamFile.js +55 -23
  6. package/dist/bamFile.js.map +1 -1
  7. package/dist/chunk.js +5 -0
  8. package/dist/chunk.js.map +1 -1
  9. package/dist/csi.js +4 -6
  10. package/dist/csi.js.map +1 -1
  11. package/dist/htsget.js +2 -0
  12. package/dist/htsget.js.map +1 -1
  13. package/dist/indexFile.js +2 -0
  14. package/dist/indexFile.js.map +1 -1
  15. package/dist/nullFilehandle.d.ts +4 -4
  16. package/dist/nullIndex.d.ts +4 -4
  17. package/dist/record.d.ts +1 -1
  18. package/dist/record.js +17 -2
  19. package/dist/record.js.map +1 -1
  20. package/dist/virtualOffset.js +2 -0
  21. package/dist/virtualOffset.js.map +1 -1
  22. package/esm/bai.js +1 -0
  23. package/esm/bai.js.map +1 -1
  24. package/esm/bamFile.d.ts +1 -0
  25. package/esm/bamFile.js +55 -23
  26. package/esm/bamFile.js.map +1 -1
  27. package/esm/chunk.js +5 -0
  28. package/esm/chunk.js.map +1 -1
  29. package/esm/csi.js +4 -6
  30. package/esm/csi.js.map +1 -1
  31. package/esm/htsget.js +2 -0
  32. package/esm/htsget.js.map +1 -1
  33. package/esm/indexFile.js +2 -0
  34. package/esm/indexFile.js.map +1 -1
  35. package/esm/nullFilehandle.d.ts +4 -4
  36. package/esm/nullIndex.d.ts +4 -4
  37. package/esm/record.d.ts +1 -1
  38. package/esm/record.js +17 -2
  39. package/esm/record.js.map +1 -1
  40. package/esm/virtualOffset.js +2 -0
  41. package/esm/virtualOffset.js.map +1 -1
  42. package/package.json +22 -21
  43. package/src/bamFile.ts +52 -24
  44. package/src/nullFilehandle.ts +4 -4
  45. package/src/nullIndex.ts +4 -4
  46. package/src/record.ts +7 -3
  47. package/CHANGELOG.md +0 -532
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@gmod/bam",
3
- "version": "7.1.18",
3
+ "version": "7.1.20",
4
4
  "description": "Parser for BAM and BAM index (bai) files",
5
5
  "license": "MIT",
6
- "repository": "GMOD/bam-js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/GMOD/bam-js.git"
9
+ },
7
10
  "type": "module",
8
- "types": "./dist/index.d.ts",
11
+ "types": "./esm/index.d.ts",
9
12
  "exports": {
10
- "import": {
11
- "import": "./esm/index.js"
12
- },
13
- "require": {
13
+ ".": {
14
+ "import": "./esm/index.js",
14
15
  "require": "./dist/index.js"
15
16
  }
16
17
  },
@@ -20,7 +21,7 @@
20
21
  "url": "https://github.com/cmdcolin"
21
22
  },
22
23
  "engines": {
23
- "node": ">=6"
24
+ "node": ">=20"
24
25
  },
25
26
  "files": [
26
27
  "dist",
@@ -35,11 +36,11 @@
35
36
  "clean": "rimraf dist esm",
36
37
  "format": "prettier --write .",
37
38
  "build:esm": "tsc --outDir esm",
38
- "build:es5": "tsc --module commonjs --outDir dist",
39
- "build": "yarn build:esm && yarn build:es5",
40
- "prebuild": "yarn clean",
39
+ "build:es5": "tsc --module commonjs --moduleResolution bundler --outDir dist",
40
+ "build": "pnpm build:esm && pnpm build:es5",
41
+ "prebuild": "pnpm clean",
41
42
  "postbuild:es5": "echo '{\"type\": \"commonjs\"}' > dist/package.json",
42
- "preversion": "yarn lint && yarn test --run && yarn build",
43
+ "preversion": "pnpm lint && pnpm test --run && pnpm build",
43
44
  "version": "standard-changelog && git add CHANGELOG.md",
44
45
  "postversion": "git push --follow-tags"
45
46
  },
@@ -53,21 +54,21 @@
53
54
  "@gmod/bgzf-filehandle": "^6.0.12",
54
55
  "@jbrowse/quick-lru": "^7.3.5",
55
56
  "crc": "^4.3.2",
56
- "generic-filehandle2": "^2.0.18"
57
+ "generic-filehandle2": "^2.1.4"
57
58
  },
58
59
  "devDependencies": {
59
60
  "@eslint/js": "^10.0.1",
60
- "@types/node": "^25.3.3",
61
- "@vitest/coverage-v8": "^4.0.18",
62
- "eslint": "^10.0.2",
63
- "eslint-plugin-import": "^2.31.0",
64
- "eslint-plugin-unicorn": "^63.0.0",
61
+ "@types/node": "^25.5.0",
62
+ "@vitest/coverage-v8": "^4.1.2",
63
+ "eslint": "^9.39.4",
64
+ "eslint-plugin-import": "^2.32.0",
65
+ "eslint-plugin-unicorn": "^64.0.0",
65
66
  "prettier": "^3.8.1",
66
67
  "rimraf": "^6.1.3",
67
68
  "standard-changelog": "^7.0.1",
68
- "typescript": "^5.0.4",
69
- "typescript-eslint": "^8.56.1",
70
- "vitest": "^4.0.18"
69
+ "typescript": "^6.0.2",
70
+ "typescript-eslint": "^8.57.2",
71
+ "vitest": "^4.1.2"
71
72
  },
72
73
  "publishConfig": {
73
74
  "access": "public"
package/src/bamFile.ts CHANGED
@@ -139,32 +139,49 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
139
139
 
140
140
  // firstDataLine is not defined in cases where there is no data in the file
141
141
  // (just bam header and nothing else)
142
- const buffer =
142
+ let readLen =
143
143
  indexData.firstDataLine === undefined
144
+ ? undefined
145
+ : indexData.firstDataLine.blockPosition + blockLen
146
+
147
+ const buffer =
148
+ readLen === undefined
144
149
  ? await this.bam.readFile()
145
- : // the logic indexData.firstDataLine is a virtualOffset telling us
146
- // where the data is. It is in the middle of a virtualOffset
147
- // (provided by the bgzip block offset at blockPosition + the
148
- // virtualOffset dataPosition, so we add one extra blockLen to make
149
- // sure we consume the full header)
150
- await this.bam.read(
151
- indexData.firstDataLine.blockPosition + blockLen,
152
- 0,
153
- )
154
- const uncba = await unzip(buffer)
150
+ : await this.bam.read(readLen, 0)
151
+ let uncba = await unzip(buffer)
155
152
  const dataView = new DataView(uncba.buffer)
156
153
 
157
154
  if (dataView.getInt32(0, true) !== BAM_MAGIC) {
158
155
  throw new Error('Not a BAM file')
159
156
  }
160
157
  const headLen = dataView.getInt32(4, true)
161
- const decoder = new TextDecoder('utf8')
162
- this.header = decoder.decode(uncba.subarray(8, 8 + headLen))
163
-
164
- const { chrToIndex, indexToChr } = this._parseRefSeqs(uncba, headLen + 8)
165
- this.chrToIndex = chrToIndex
166
- this.indexToChr = indexToChr
167
- return parseHeaderText(this.header)
158
+ this.header = new TextDecoder('utf8').decode(uncba.subarray(8, 8 + headLen))
159
+
160
+ // BAM files with many reference sequences may need more data than the
161
+ // initial read provides, so re-fetch with doubled size until it fits
162
+ const refSeqStart = headLen + 8
163
+ const maxRetries = 5
164
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
165
+ if (this._hasEnoughRefSeqData(uncba, refSeqStart)) {
166
+ const { chrToIndex, indexToChr } = this._parseRefSeqs(
167
+ uncba,
168
+ refSeqStart,
169
+ )
170
+ this.chrToIndex = chrToIndex
171
+ this.indexToChr = indexToChr
172
+ return parseHeaderText(this.header)
173
+ }
174
+ if (readLen === undefined) {
175
+ throw new Error(
176
+ `Insufficient data for reference sequences in ${uncba.length} bytes`,
177
+ )
178
+ }
179
+ readLen *= 2
180
+ uncba = await unzip(await this.bam.read(readLen, 0))
181
+ }
182
+ throw new Error(
183
+ `Insufficient data for reference sequences after ${maxRetries} retries`,
184
+ )
168
185
  }
169
186
 
170
187
  getHeader(opts?: BaseOpts) {
@@ -182,6 +199,23 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
182
199
  return this.header
183
200
  }
184
201
 
202
+ _hasEnoughRefSeqData(uncba: Uint8Array, start: number) {
203
+ if (start + 4 > uncba.length) {
204
+ return false
205
+ }
206
+ const dataView = new DataView(uncba.buffer)
207
+ const nRef = dataView.getInt32(start, true)
208
+ let p = start + 4
209
+ for (let i = 0; i < nRef; i += 1) {
210
+ if (p + 8 > uncba.length) {
211
+ return false
212
+ }
213
+ const lName = dataView.getInt32(p, true)
214
+ p = p + 8 + lName
215
+ }
216
+ return true
217
+ }
218
+
185
219
  _parseRefSeqs(
186
220
  uncba: Uint8Array,
187
221
  start: number,
@@ -198,12 +232,6 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
198
232
  const decoder = new TextDecoder('utf8')
199
233
 
200
234
  for (let i = 0; i < nRef; i += 1) {
201
- if (p + 8 > uncba.length) {
202
- throw new Error(
203
- `Insufficient data for reference sequences: need more than ${uncba.length} bytes`,
204
- )
205
- }
206
-
207
235
  const lName = dataView.getInt32(p, true)
208
236
  const refName = this.renameRefSeq(
209
237
  decoder.decode(uncba.subarray(p + 4, p + 4 + lName - 1)),
@@ -1,16 +1,16 @@
1
1
  export default class NullFilehandle {
2
- public read(): Promise<any> {
2
+ public read(): Promise<never> {
3
3
  throw new Error('never called')
4
4
  }
5
- public stat(): Promise<any> {
5
+ public stat(): Promise<never> {
6
6
  throw new Error('never called')
7
7
  }
8
8
 
9
- public readFile(): Promise<any> {
9
+ public readFile(): Promise<never> {
10
10
  throw new Error('never called')
11
11
  }
12
12
 
13
- public close(): Promise<any> {
13
+ public close(): Promise<never> {
14
14
  throw new Error('never called')
15
15
  }
16
16
  }
package/src/nullIndex.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  import IndexFile from './indexFile.ts'
2
2
 
3
3
  export default class NullIndex extends IndexFile {
4
- public lineCount(): Promise<any> {
4
+ public lineCount(): Promise<never> {
5
5
  throw new Error('never called')
6
6
  }
7
- protected _parse(): Promise<any> {
7
+ protected _parse(): Promise<never> {
8
8
  throw new Error('never called')
9
9
  }
10
10
 
11
- public async indexCov(): Promise<any> {
11
+ public async indexCov(): Promise<never> {
12
12
  throw new Error('never called')
13
13
  }
14
14
 
15
- public blocksForRange(): Promise<any> {
15
+ public blocksForRange(): Promise<never> {
16
16
  throw new Error('never called')
17
17
  }
18
18
  }
package/src/record.ts CHANGED
@@ -674,10 +674,14 @@ export default class BamRecord {
674
674
  }
675
675
 
676
676
  // adapted from igv.js
677
- // uses precomputed lookup table indexed by flag bits + isize sign
677
+ // uses precomputed lookup table indexed by flag bits + isize sign.
678
+ // the BAM spec defines tlen as positive for the leftmost segment and
679
+ // negative for the rightmost, so tlen > 0 reliably indicates which
680
+ // read comes first without needing position-based correction
681
+ // (see also: gmod/cram-js src/cramFile/record.ts getPairOrientation)
678
682
  get pair_orientation() {
679
683
  const f = this.flags
680
- // combined check: unmapped (0x4) clear, mate unmapped (0x8) clear
684
+ // unmapped (0x4) or mate unmapped (0x8) -> undefined
681
685
  if (f & 0xc || this.ref_id !== this.next_refid) {
682
686
  return undefined
683
687
  }
@@ -727,7 +731,7 @@ export default class BamRecord {
727
731
  }
728
732
 
729
733
  toJSON() {
730
- const data: Record<string, any> = {}
734
+ const data: Record<string, unknown> = {}
731
735
  for (const k of Object.keys(this)) {
732
736
  if (k.startsWith('_') || k === 'bytes') {
733
737
  continue