@gmod/tabix 3.5.4 → 3.5.7

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/util.ts CHANGED
@@ -5,6 +5,37 @@ import { longFromBytesToUnsigned } from './long.ts'
5
5
  import VirtualOffset from './virtualOffset.ts'
6
6
 
7
7
  // SYNC: ~/src/gmod/bam-js/src/util.ts optimizeChunks
8
+ /**
9
+ * `signal.throwIfAborted()`, without requiring either that method or `reason`.
10
+ *
11
+ * Two reasons not to call the built-in directly. It assumes a *real*
12
+ * `AbortSignal`, and callers pass duck-typed ones, where calling a missing
13
+ * method is a `TypeError` rather than the cancellation the caller asked for —
14
+ * a strictly worse failure.
15
+ *
16
+ * And it sets a browser floor. `AbortSignal.prototype.throwIfAborted` and
17
+ * `AbortSignal.reason` are Safari 15.4 / Chrome 100 / Firefox 97 (March 2022),
18
+ * higher than anything else here needs: this package otherwise touches only
19
+ * `.aborted`, and `generic-filehandle2` only forwards a signal to `fetch`.
20
+ *
21
+ * Faithful to the spec otherwise: an aborted signal throws its `reason`
22
+ * whatever that is, and only synthesizes an `AbortError` when there is none.
23
+ * Kept in sync with the copy in `@gmod/bam`'s `src/util.ts`.
24
+ */
25
+ export function throwIfAborted(signal?: AbortSignal) {
26
+ if (signal?.aborted) {
27
+ const reason: unknown = signal.reason
28
+ // Spec-faithful: throwIfAborted throws `reason` verbatim, and `reason` is
29
+ // whatever the caller passed to abort() — `controller.abort('too slow')`
30
+ // makes it a string. Coercing it to an Error here would hide that from a
31
+ // consumer who set it deliberately.
32
+ // eslint-disable-next-line @typescript-eslint/only-throw-error
33
+ throw reason === undefined
34
+ ? new DOMException('This operation was aborted', 'AbortError')
35
+ : reason
36
+ }
37
+ }
38
+
8
39
  export function optimizeChunks(chunks: Chunk[], lowest?: VirtualOffset) {
9
40
  const n = chunks.length
10
41
  if (n === 0) {
@@ -75,7 +106,49 @@ export function optimizeChunks(chunks: Chunk[], lowest?: VirtualOffset) {
75
106
  }
76
107
  }
77
108
 
78
- return mergedChunks
109
+ return makeDisjoint(mergedChunks)
110
+ }
111
+
112
+ // SYNC: ~/src/gmod/bam-js/src/util.ts makeDisjoint
113
+ /**
114
+ * Trim any chunk that starts before its predecessor ends, so the spans a query
115
+ * reads never overlap.
116
+ *
117
+ * Merging alone does not guarantee this. Two chunks from different bins can
118
+ * overlap inside a single BGZF block while the merge above still declines to
119
+ * join them, because the combined span would exceed its 5MB cap. The shared
120
+ * bytes are then fetched, decompressed and decoded by both chunks, and every
121
+ * line in the overlap is returned TWICE from getLines — a duplicated VCF
122
+ * record or GFF feature, with a duplicated fileOffset.
123
+ *
124
+ * Safe because a chunk's `maxv` is the virtual offset just past its last
125
+ * record — a record boundary — so no record begins inside the trimmed span and
126
+ * the union of the chunks is unchanged.
127
+ *
128
+ * No fixture here reproduces it (@gmod/bam found it on an 18MB BAM, where
129
+ * out.bam returned 5 of its 6551 records twice); this keeps the two copies of
130
+ * optimizeChunks in step and pins the invariant.
131
+ */
132
+ function makeDisjoint(chunks: Chunk[]) {
133
+ const out: Chunk[] = [chunks[0]!]
134
+ for (let i = 1; i < chunks.length; i++) {
135
+ const chunk = chunks[i]!
136
+ const prevMax = out.at(-1)!.maxv
137
+ const cmp =
138
+ chunk.minv.blockPosition - prevMax.blockPosition ||
139
+ chunk.minv.dataPosition - prevMax.dataPosition
140
+ if (cmp >= 0) {
141
+ out.push(chunk)
142
+ continue
143
+ }
144
+ const stillHasData =
145
+ chunk.maxv.blockPosition - prevMax.blockPosition ||
146
+ chunk.maxv.dataPosition - prevMax.dataPosition
147
+ if (stillHasData > 0) {
148
+ out.push(new Chunk(prevMax, chunk.maxv, chunk.bin, chunk.endPosition))
149
+ }
150
+ }
151
+ return out
79
152
  }
80
153
 
81
154
  // Tighten each chunk's endPosition (default: a full max-size BGZF block past