@gmod/bam 2.0.4 → 3.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.
package/src/bai.ts CHANGED
@@ -22,7 +22,7 @@ function reg2bins(beg: number, end: number) {
22
22
  [73 + (beg >> 20), 73 + (end >> 20)],
23
23
  [585 + (beg >> 17), 585 + (end >> 17)],
24
24
  [4681 + (beg >> 14), 4681 + (end >> 14)],
25
- ]
25
+ ] as const
26
26
  }
27
27
 
28
28
  export default class BAI extends IndexFile {
@@ -125,6 +125,7 @@ export default class BAI extends IndexFile {
125
125
  const range = start !== undefined
126
126
  const indexData = await this.parse(opts)
127
127
  const seqIdx = indexData.indices[seqId]
128
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
128
129
  if (!seqIdx) {
129
130
  return []
130
131
  }
@@ -167,10 +168,12 @@ export default class BAI extends IndexFile {
167
168
  }
168
169
 
169
170
  const indexData = await this.parse(opts)
171
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
170
172
  if (!indexData) {
171
173
  return []
172
174
  }
173
175
  const ba = indexData.indices[refId]
176
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
174
177
  if (!ba) {
175
178
  return []
176
179
  }
@@ -182,10 +185,11 @@ export default class BAI extends IndexFile {
182
185
  // Find chunks in overlapping bins. Leaf bins (< 4681) are not pruned
183
186
  for (const [start, end] of overlappingBins) {
184
187
  for (let bin = start; bin <= end; bin++) {
188
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
185
189
  if (ba.binIndex[bin]) {
186
190
  const binChunks = ba.binIndex[bin]
187
191
  for (const binChunk of binChunks) {
188
- chunks.push(binChunk)
192
+ chunks.push(new Chunk(binChunk.minv, binChunk.maxv, bin))
189
193
  }
190
194
  }
191
195
  }
@@ -199,6 +203,8 @@ export default class BAI extends IndexFile {
199
203
  const maxLin = Math.min(max >> 14, nintv - 1)
200
204
  for (let i = minLin; i <= maxLin; ++i) {
201
205
  const vp = ba.linearIndex[i]
206
+
207
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
202
208
  if (vp && (!lowest || vp.compareTo(lowest) < 0)) {
203
209
  lowest = vp
204
210
  }
@@ -209,7 +215,7 @@ export default class BAI extends IndexFile {
209
215
 
210
216
  async parse(opts: BaseOpts = {}) {
211
217
  if (!this.setupP) {
212
- this.setupP = this._parse(opts).catch(e => {
218
+ this.setupP = this._parse(opts).catch((e: unknown) => {
213
219
  this.setupP = undefined
214
220
  throw e
215
221
  })
package/src/bamFile.ts CHANGED
@@ -178,7 +178,7 @@ export default class BamFile {
178
178
 
179
179
  getHeader(opts?: BaseOpts) {
180
180
  if (!this.headerP) {
181
- this.headerP = this.getHeaderPre(opts).catch(e => {
181
+ this.headerP = this.getHeaderPre(opts).catch((e: unknown) => {
182
182
  this.headerP = undefined
183
183
  throw e
184
184
  })
@@ -288,12 +288,12 @@ export default class BamFile {
288
288
 
289
289
  const recs = [] as BAMFeature[]
290
290
  for (const feature of records) {
291
- if (feature.seq_id() === chrId) {
292
- if (feature.get('start') >= max) {
291
+ if (feature.ref_id === chrId) {
292
+ if (feature.start >= max) {
293
293
  // past end of range, can stop iterating
294
294
  done = true
295
295
  break
296
- } else if (feature.get('end') >= min) {
296
+ } else if (feature.end >= min) {
297
297
  // must be in range
298
298
  recs.push(feature)
299
299
  }
@@ -319,8 +319,8 @@ export default class BamFile {
319
319
  feats.map(ret => {
320
320
  const readNames: Record<string, number> = {}
321
321
  for (const element of ret) {
322
- const name = element.name()
323
- const id = element.id()
322
+ const name = element.name
323
+ const id = element.id
324
324
  if (!readNames[name]) {
325
325
  readNames[name] = 0
326
326
  }
@@ -337,10 +337,10 @@ export default class BamFile {
337
337
  const matePromises: Promise<Chunk[]>[] = []
338
338
  feats.map(ret => {
339
339
  for (const f of ret) {
340
- const name = f.name()
341
- const start = f.get('start')
342
- const pnext = f._next_pos()
343
- const rnext = f._next_refid()
340
+ const name = f.name
341
+ const start = f.start
342
+ const pnext = f.next_pos
343
+ const rnext = f.next_refid
344
344
  if (
345
345
  this.index &&
346
346
  unmatedPairs[name] &&
@@ -377,7 +377,7 @@ export default class BamFile {
377
377
  dpositions,
378
378
  chunk,
379
379
  )) {
380
- if (unmatedPairs[feature.get('name')] && !readIds[feature.id()]) {
380
+ if (unmatedPairs[feature.name] && !readIds[feature.id]) {
381
381
  mateRecs.push(feature)
382
382
  }
383
383
  }
@@ -430,6 +430,7 @@ export default class BamFile {
430
430
  const blockEnd = blockStart + 4 + blockSize - 1
431
431
 
432
432
  // increment position to the current decompressed status
433
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
433
434
  if (dpositions) {
434
435
  while (blockStart + chunk.minv.dataPosition >= dpositions[pos++]) {}
435
436
  pos--
@@ -470,6 +471,7 @@ export default class BamFile {
470
471
  chunk.minv.dataPosition +
471
472
  1
472
473
  : // must be slice, not subarray for buffer polyfill on web
474
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
473
475
  crc32.signed(ba.slice(blockStart, blockEnd)),
474
476
  })
475
477
 
package/src/csi.ts CHANGED
@@ -159,7 +159,8 @@ export default class CSI extends IndexFile {
159
159
  }
160
160
 
161
161
  const indexData = await this.parse(opts)
162
- const ba = indexData?.indices[refId]
162
+ const ba = indexData.indices[refId]
163
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
163
164
  if (!ba) {
164
165
  return []
165
166
  }
@@ -173,6 +174,7 @@ export default class CSI extends IndexFile {
173
174
  // Find chunks in overlapping bins. Leaf bins (< 4681) are not pruned
174
175
  for (const [start, end] of overlappingBins) {
175
176
  for (let bin = start; bin <= end; bin++) {
177
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
176
178
  if (ba.binIndex[bin]) {
177
179
  const binChunks = ba.binIndex[bin]
178
180
  for (const c of binChunks) {
@@ -210,14 +212,14 @@ export default class CSI extends IndexFile {
210
212
  `query ${beg}-${end} is too large for current binning scheme (shift ${this.minShift}, depth ${this.depth}), try a smaller query or a coarser index binning scheme`,
211
213
  )
212
214
  }
213
- bins.push([b, e])
215
+ bins.push([b, e] as const)
214
216
  }
215
217
  return bins
216
218
  }
217
219
 
218
220
  async parse(opts: BaseOpts = {}) {
219
221
  if (!this.setupP) {
220
- this.setupP = this._parse(opts).catch(e => {
222
+ this.setupP = this._parse(opts).catch((e: unknown) => {
221
223
  this.setupP = undefined
222
224
  throw e
223
225
  })
package/src/record.ts CHANGED
@@ -1,202 +1,102 @@
1
1
  import Constants from './constants'
2
+ import type { Buffer } from 'buffer'
2
3
 
3
4
  const SEQRET_DECODER = '=ACMGRSVTWYHKDBN'.split('')
4
5
  const CIGAR_DECODER = 'MIDNSHP=X???????'.split('')
5
6
 
6
- /**
7
- * Class of each BAM record returned by this API.
8
- */
7
+ interface Bytes {
8
+ start: number
9
+ end: number
10
+ byteArray: Buffer
11
+ }
9
12
  export default class BamRecord {
10
- private data = {} as Record<string, any>
11
- private bytes: { start: number; end: number; byteArray: Buffer }
12
- private _id: number
13
- private _tagOffset: number | undefined
14
- private _tagList: string[] = []
15
- private _allTagsParsed = false
16
-
17
- public flags: any
18
- public _refID: number
19
- constructor(args: any) {
20
- const { bytes, fileOffset } = args
21
- const { byteArray, start } = bytes
22
- this.data = { start: byteArray.readInt32LE(start + 8) }
23
- this.bytes = bytes
24
- this._id = fileOffset
25
- this._refID = byteArray.readInt32LE(start + 4)
26
- this.flags = (byteArray.readInt32LE(start + 16) & 0xffff0000) >> 16
27
- }
28
-
29
- get(field: string) {
30
- //@ts-ignore
31
- if (this[field]) {
32
- //@ts-ignore
33
- if (this.data[field]) {
34
- return this.data[field]
35
- }
36
- //@ts-ignore
37
- this.data[field] = this[field]()
38
- return this.data[field]
39
- }
40
- return this._get(field.toLowerCase())
41
- }
13
+ public fileOffset: number
14
+ private bytes: Bytes
42
15
 
43
- end() {
44
- return this.get('start') + this.get('length_on_ref')
16
+ constructor(args: { bytes: Bytes; fileOffset: number }) {
17
+ this.bytes = args.bytes
18
+ this.fileOffset = args.fileOffset
45
19
  }
46
20
 
47
- seq_id() {
48
- return this._refID
21
+ get byteArray() {
22
+ return this.bytes.byteArray
49
23
  }
50
24
 
51
- // same as get(), except requires lower-case arguments. used
52
- // internally to save lots of calls to field.toLowerCase()
53
- _get(field: string) {
54
- if (field in this.data) {
55
- return this.data[field]
56
- }
57
- this.data[field] = this._parseTag(field)
58
- return this.data[field]
59
- }
60
-
61
- _tags() {
62
- this._parseAllTags()
63
-
64
- let tags = ['seq']
65
-
66
- if (!this.isSegmentUnmapped()) {
67
- tags.push(
68
- 'start',
69
- 'end',
70
- 'strand',
71
- 'score',
72
- 'qual',
73
- 'MQ',
74
- 'CIGAR',
75
- 'length_on_ref',
76
- 'template_length',
77
- )
78
- }
79
- if (this.isPaired()) {
80
- tags.push('next_segment_position', 'pair_orientation')
81
- }
82
- tags = tags.concat(this._tagList || [])
83
-
84
- for (const k of Object.keys(this.data)) {
85
- if (!k.startsWith('_') && k !== 'next_seq_id') {
86
- tags.push(k)
87
- }
88
- }
89
-
90
- const seen: Record<string, boolean> = {}
91
- return tags.filter(t => {
92
- if (
93
- (t in this.data && this.data[t] === undefined) ||
94
- t === 'CG' ||
95
- t === 'cg'
96
- ) {
97
- return false
98
- }
99
-
100
- const lt = t.toLowerCase()
101
- const s = seen[lt]
102
- seen[lt] = true
103
- return !s
104
- })
25
+ get flags() {
26
+ return (
27
+ (this.byteArray.readInt32LE(this.bytes.start + 16) & 0xffff0000) >> 16
28
+ )
29
+ }
30
+ get ref_id() {
31
+ return this.byteArray.readInt32LE(this.bytes.start + 4)
105
32
  }
106
33
 
107
- parent() {
108
- return
34
+ get start() {
35
+ return this.byteArray.readInt32LE(this.bytes.start + 8)
109
36
  }
110
37
 
111
- children() {
112
- return this.get('subfeatures')
38
+ get end() {
39
+ return this.start + this.length_on_ref
113
40
  }
114
41
 
115
- id() {
116
- return this._id
42
+ get id() {
43
+ return this.fileOffset
117
44
  }
118
45
 
119
- // special parsers
120
- /**
121
- * Mapping quality score.
122
- */
123
- mq() {
124
- const mq = (this.get('_bin_mq_nl') & 0xff00) >> 8
46
+ get mq() {
47
+ const mq = (this.bin_mq_nl & 0xff00) >> 8
125
48
  return mq === 255 ? undefined : mq
126
49
  }
127
50
 
128
- score() {
129
- return this.get('mq')
51
+ get score() {
52
+ return this.mq
130
53
  }
131
54
 
132
- qual() {
133
- return this.qualRaw()?.join(' ')
55
+ get qual() {
56
+ return this.qualRaw?.join(' ')
134
57
  }
135
58
 
136
- qualRaw() {
59
+ get qualRaw() {
137
60
  if (this.isSegmentUnmapped()) {
138
61
  return
139
62
  }
140
63
 
141
- const { start, byteArray } = this.bytes
142
64
  const p =
143
- start +
144
- 36 +
145
- this.get('_l_read_name') +
146
- this.get('_n_cigar_op') * 4 +
147
- this.get('_seq_bytes')
148
- const lseq = this.get('seq_length')
149
- return byteArray.subarray(p, p + lseq)
65
+ this.b0 +
66
+ this.read_name_length +
67
+ this.num_cigar_ops * 4 +
68
+ this.num_seq_bytes
69
+ return this.byteArray.subarray(p, p + this.seq_length)
150
70
  }
151
71
 
152
- strand() {
72
+ get strand() {
153
73
  return this.isReverseComplemented() ? -1 : 1
154
74
  }
155
75
 
156
- multi_segment_next_segment_strand() {
157
- if (this.isMateUnmapped()) {
158
- return
159
- }
160
- return this.isMateReverseComplemented() ? -1 : 1
161
- }
162
-
163
- name() {
164
- return this.get('_read_name')
76
+ get b0() {
77
+ return this.bytes.start + 36
165
78
  }
166
-
167
- _read_name() {
168
- const nl = this.get('_l_read_name')
169
- const { byteArray, start } = this.bytes
170
- return byteArray.toString('ascii', start + 36, start + 36 + nl - 1)
79
+ get name() {
80
+ return this.byteArray.toString(
81
+ 'ascii',
82
+ this.b0,
83
+ this.b0 + this.read_name_length - 1,
84
+ )
171
85
  }
172
86
 
173
- /**
174
- * Get the value of a tag, parsing the tags as far as necessary.
175
- * Only called if we have not already parsed that field.
176
- */
177
- _parseTag(tagName?: string) {
178
- // if all of the tags have been parsed and we're still being
179
- // called, we already know that we have no such tag, because
180
- // it would already have been cached.
181
- if (this._allTagsParsed) {
182
- return
183
- }
184
-
185
- const { byteArray, start } = this.bytes
87
+ get tags() {
88
+ const { byteArray } = this.bytes
186
89
  let p =
187
- this._tagOffset ||
188
- start +
189
- 36 +
190
- this.get('_l_read_name') +
191
- this.get('_n_cigar_op') * 4 +
192
- this.get('_seq_bytes') +
193
- this.get('seq_length')
90
+ this.b0 +
91
+ this.read_name_length +
92
+ this.num_cigar_ops * 4 +
93
+ this.num_seq_bytes +
94
+ this.seq_length
194
95
 
195
96
  const blockEnd = this.bytes.end
196
- let lcTag
197
- while (p < blockEnd && lcTag !== tagName) {
97
+ const tags = {} as Record<string, unknown>
98
+ while (p < blockEnd) {
198
99
  const tag = String.fromCharCode(byteArray[p], byteArray[p + 1])
199
- lcTag = tag.toLowerCase()
200
100
  const type = String.fromCharCode(byteArray[p + 2])
201
101
  p += 3
202
102
 
@@ -353,31 +253,9 @@ export default class BamRecord {
353
253
  } // stop parsing tags
354
254
  }
355
255
 
356
- this._tagOffset = p
357
-
358
- this._tagList.push(tag)
359
- if (lcTag === tagName) {
360
- return value
361
- }
362
-
363
- this.data[lcTag] = value
256
+ tags[tag] = value
364
257
  }
365
- this._allTagsParsed = true
366
- return
367
- }
368
-
369
- _parseAllTags() {
370
- this._parseTag('')
371
- }
372
-
373
- _parseCigar(cigar: string) {
374
- return (
375
- //@ts-ignore
376
- cigar
377
- .match(/\d+\D/g)
378
- //@ts-ignore
379
- .map(op => [/\D/.exec(op)[0].toUpperCase(), Number.parseInt(op, 10)])
380
- )
258
+ return tags
381
259
  }
382
260
 
383
261
  /**
@@ -443,42 +321,44 @@ export default class BamRecord {
443
321
  return !!(this.flags & Constants.BAM_FSUPPLEMENTARY)
444
322
  }
445
323
 
446
- cigar() {
324
+ get cigarAndLength() {
447
325
  if (this.isSegmentUnmapped()) {
448
- return
326
+ return {
327
+ length_on_ref: 0,
328
+ CIGAR: '',
329
+ }
449
330
  }
450
331
 
451
- const { byteArray, start } = this.bytes
452
- const numCigarOps = this.get('_n_cigar_op')
453
- let p = start + 36 + this.get('_l_read_name')
454
- const seqLen = this.get('seq_length')
455
- let cigar = ''
456
- let lref = 0
332
+ const numCigarOps = this.num_cigar_ops
333
+ let p = this.b0 + this.read_name_length
334
+ let CIGAR = ''
457
335
 
458
- // check for CG tag by inspecting whether the CIGAR field
459
- // contains a clip that consumes entire seqLen
460
- let cigop = byteArray.readInt32LE(p)
336
+ // check for CG tag by inspecting whether the CIGAR field contains a clip
337
+ // that consumes entire seqLen
338
+ let cigop = this.byteArray.readInt32LE(p)
461
339
  let lop = cigop >> 4
462
340
  let op = CIGAR_DECODER[cigop & 0xf]
463
- if (op === 'S' && lop === seqLen) {
464
- // if there is a CG the second CIGAR field will
465
- // be a N tag the represents the length on ref
341
+ if (op === 'S' && lop === this.seq_length) {
342
+ // if there is a CG the second CIGAR field will be a N tag the represents
343
+ // the length on ref
466
344
  p += 4
467
- cigop = byteArray.readInt32LE(p)
345
+ cigop = this.byteArray.readInt32LE(p)
468
346
  lop = cigop >> 4
469
347
  op = CIGAR_DECODER[cigop & 0xf]
470
348
  if (op !== 'N') {
471
349
  console.warn('CG tag with no N tag')
472
350
  }
473
- this.data.length_on_ref = lop
474
- return this.get('CG')
351
+ return {
352
+ CIGAR: this.tags.CG as string,
353
+ length_on_ref: lop,
354
+ }
475
355
  } else {
356
+ let lref = 0
476
357
  for (let c = 0; c < numCigarOps; ++c) {
477
- cigop = byteArray.readInt32LE(p)
358
+ cigop = this.byteArray.readInt32LE(p)
478
359
  lop = cigop >> 4
479
360
  op = CIGAR_DECODER[cigop & 0xf]
480
- cigar += lop + op
481
-
361
+ CIGAR += lop + op
482
362
  // soft clip, hard clip, and insertion don't count toward
483
363
  // the length on the reference
484
364
  if (op !== 'H' && op !== 'S' && op !== 'I') {
@@ -488,45 +368,38 @@ export default class BamRecord {
488
368
  p += 4
489
369
  }
490
370
 
491
- this.data.length_on_ref = lref
492
- return cigar
371
+ return {
372
+ CIGAR,
373
+ length_on_ref: lref,
374
+ }
493
375
  }
494
376
  }
495
377
 
496
- length_on_ref() {
497
- if (this.data.length_on_ref) {
498
- return this.data.length_on_ref
499
- } else {
500
- this.get('cigar') // the length_on_ref is set as a side effect
501
- return this.data.length_on_ref
502
- }
378
+ get length_on_ref() {
379
+ return this.cigarAndLength.length_on_ref
503
380
  }
504
381
 
505
- _n_cigar_op() {
506
- return this.get('_flag_nc') & 0xffff
382
+ get CIGAR() {
383
+ return this.cigarAndLength.CIGAR
507
384
  }
508
385
 
509
- _l_read_name() {
510
- return this.get('_bin_mq_nl') & 0xff
386
+ get num_cigar_ops() {
387
+ return this.flag_nc & 0xffff
511
388
  }
512
389
 
513
- /**
514
- * number of bytes in the sequence field
515
- */
516
- _seq_bytes() {
517
- return (this.get('seq_length') + 1) >> 1
390
+ get read_name_length() {
391
+ return this.bin_mq_nl & 0xff
518
392
  }
519
393
 
520
- getReadBases() {
521
- return this.seq()
394
+ get num_seq_bytes() {
395
+ return (this.seq_length + 1) >> 1
522
396
  }
523
397
 
524
- seq() {
525
- const { byteArray, start } = this.bytes
526
- const p =
527
- start + 36 + this.get('_l_read_name') + this.get('_n_cigar_op') * 4
528
- const seqBytes = this.get('_seq_bytes')
529
- const len = this.get('seq_length')
398
+ get seq() {
399
+ const { byteArray } = this.bytes
400
+ const p = this.b0 + this.read_name_length + this.num_cigar_ops * 4
401
+ const seqBytes = this.num_seq_bytes
402
+ const len = this.seq_length
530
403
  let buf = ''
531
404
  let i = 0
532
405
  for (let j = 0; j < seqBytes; ++j) {
@@ -542,11 +415,11 @@ export default class BamRecord {
542
415
  }
543
416
 
544
417
  // adapted from igv.js
545
- getPairOrientation() {
418
+ get pair_orientation() {
546
419
  if (
547
420
  !this.isSegmentUnmapped() &&
548
421
  !this.isMateUnmapped() &&
549
- this._refID === this._next_refid()
422
+ this.ref_id === this.next_refid
550
423
  ) {
551
424
  const s1 = this.isReverseComplemented() ? 'R' : 'F'
552
425
  const s2 = this.isMateReverseComplemented() ? 'R' : 'F'
@@ -561,7 +434,7 @@ export default class BamRecord {
561
434
  }
562
435
 
563
436
  const tmp = []
564
- const isize = this.template_length()
437
+ const isize = this.template_length
565
438
  if (isize > 0) {
566
439
  tmp[0] = s1
567
440
  tmp[1] = o1
@@ -578,28 +451,28 @@ export default class BamRecord {
578
451
  return ''
579
452
  }
580
453
 
581
- _bin_mq_nl() {
582
- return this.bytes.byteArray.readInt32LE(this.bytes.start + 12)
454
+ get bin_mq_nl() {
455
+ return this.byteArray.readInt32LE(this.bytes.start + 12)
583
456
  }
584
457
 
585
- _flag_nc() {
586
- return this.bytes.byteArray.readInt32LE(this.bytes.start + 16)
458
+ get flag_nc() {
459
+ return this.byteArray.readInt32LE(this.bytes.start + 16)
587
460
  }
588
461
 
589
- seq_length() {
590
- return this.bytes.byteArray.readInt32LE(this.bytes.start + 20)
462
+ get seq_length() {
463
+ return this.byteArray.readInt32LE(this.bytes.start + 20)
591
464
  }
592
465
 
593
- _next_refid() {
594
- return this.bytes.byteArray.readInt32LE(this.bytes.start + 24)
466
+ get next_refid() {
467
+ return this.byteArray.readInt32LE(this.bytes.start + 24)
595
468
  }
596
469
 
597
- _next_pos() {
598
- return this.bytes.byteArray.readInt32LE(this.bytes.start + 28)
470
+ get next_pos() {
471
+ return this.byteArray.readInt32LE(this.bytes.start + 28)
599
472
  }
600
473
 
601
- template_length() {
602
- return this.bytes.byteArray.readInt32LE(this.bytes.start + 32)
474
+ get template_length() {
475
+ return this.byteArray.readInt32LE(this.bytes.start + 32)
603
476
  }
604
477
 
605
478
  toJSON() {
@@ -615,3 +488,27 @@ export default class BamRecord {
615
488
  return data
616
489
  }
617
490
  }
491
+
492
+ function cacheGetter<T>(ctor: { prototype: T }, prop: keyof T): void {
493
+ const desc = Object.getOwnPropertyDescriptor(ctor.prototype, prop)
494
+ if (!desc) {
495
+ throw new Error('OH NO, NO PROPERTY DESCRIPTOR')
496
+ }
497
+ // eslint-disable-next-line @typescript-eslint/unbound-method
498
+ const getter = desc.get
499
+ if (!getter) {
500
+ throw new Error('OH NO, NOT A GETTER')
501
+ }
502
+ Object.defineProperty(ctor.prototype, prop, {
503
+ get() {
504
+ const ret = getter.call(this)
505
+ Object.defineProperty(this, prop, { value: ret })
506
+ return ret
507
+ },
508
+ })
509
+ }
510
+
511
+ cacheGetter(BamRecord, 'tags')
512
+ cacheGetter(BamRecord, 'cigarAndLength')
513
+ cacheGetter(BamRecord, 'seq')
514
+ cacheGetter(BamRecord, 'qual')