jbrowse-plugin-mafviewer 1.3.2 → 1.4.0

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 (58) hide show
  1. package/README.md +0 -47
  2. package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +9 -3
  3. package/dist/LinearMafRenderer/components/LinearMafRendering.d.ts +13 -0
  4. package/dist/LinearMafRenderer/components/LinearMafRendering.js +46 -0
  5. package/dist/LinearMafRenderer/components/LinearMafRendering.js.map +1 -0
  6. package/dist/LinearMafRenderer/index.js +1 -1
  7. package/dist/LinearMafRenderer/index.js.map +1 -1
  8. package/dist/LinearMafRenderer/makeImageData.d.ts +3 -1
  9. package/dist/LinearMafRenderer/makeImageData.js +11 -4
  10. package/dist/LinearMafRenderer/makeImageData.js.map +1 -1
  11. package/dist/LinearMafRenderer/rendering/features.d.ts +0 -17
  12. package/dist/LinearMafRenderer/rendering/features.js +4 -21
  13. package/dist/LinearMafRenderer/rendering/features.js.map +1 -1
  14. package/dist/LinearMafRenderer/rendering/gaps.d.ts +1 -11
  15. package/dist/LinearMafRenderer/rendering/gaps.js +1 -17
  16. package/dist/LinearMafRenderer/rendering/gaps.js.map +1 -1
  17. package/dist/LinearMafRenderer/rendering/insertions.d.ts +1 -13
  18. package/dist/LinearMafRenderer/rendering/insertions.js +9 -15
  19. package/dist/LinearMafRenderer/rendering/insertions.js.map +1 -1
  20. package/dist/LinearMafRenderer/rendering/matches.d.ts +1 -12
  21. package/dist/LinearMafRenderer/rendering/matches.js +8 -15
  22. package/dist/LinearMafRenderer/rendering/matches.js.map +1 -1
  23. package/dist/LinearMafRenderer/rendering/mismatches.d.ts +1 -1
  24. package/dist/LinearMafRenderer/rendering/mismatches.js +14 -4
  25. package/dist/LinearMafRenderer/rendering/mismatches.js.map +1 -1
  26. package/dist/LinearMafRenderer/rendering/spatialIndex.d.ts +7 -58
  27. package/dist/LinearMafRenderer/rendering/spatialIndex.js +5 -85
  28. package/dist/LinearMafRenderer/rendering/spatialIndex.js.map +1 -1
  29. package/dist/LinearMafRenderer/rendering/types.d.ts +4 -16
  30. package/dist/LinearMafRenderer/rendering/types.js.map +1 -1
  31. package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js +37 -35
  32. package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js.map +1 -1
  33. package/dist/index.js +0 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +5 -28
  36. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
  37. package/dist/out.js +12955 -15165
  38. package/dist/out.js.map +4 -4
  39. package/package.json +3 -4
  40. package/src/LinearMafRenderer/components/{ReactComponent.tsx → LinearMafRendering.tsx} +16 -21
  41. package/src/LinearMafRenderer/index.ts +1 -1
  42. package/src/LinearMafRenderer/makeImageData.ts +20 -5
  43. package/src/LinearMafRenderer/rendering/features.ts +4 -31
  44. package/src/LinearMafRenderer/rendering/gaps.ts +0 -38
  45. package/src/LinearMafRenderer/rendering/insertions.ts +12 -28
  46. package/src/LinearMafRenderer/rendering/matches.ts +13 -30
  47. package/src/LinearMafRenderer/rendering/mismatches.ts +20 -32
  48. package/src/LinearMafRenderer/rendering/spatialIndex.ts +9 -105
  49. package/src/LinearMafRenderer/rendering/types.ts +4 -20
  50. package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +5 -6
  51. package/src/index.ts +0 -2
  52. package/src/BgzipTaffyAdapter/BgzipTaffyAdapter.ts +0 -307
  53. package/src/BgzipTaffyAdapter/configSchema.ts +0 -59
  54. package/src/BgzipTaffyAdapter/index.ts +0 -16
  55. package/src/BgzipTaffyAdapter/rowInstructions.ts +0 -91
  56. package/src/BgzipTaffyAdapter/types.ts +0 -16
  57. package/src/BgzipTaffyAdapter/util.ts +0 -25
  58. package/src/BgzipTaffyAdapter/virtualOffset.ts +0 -29
@@ -37,10 +37,7 @@ const useStyles = makeStyles()(theme => ({
37
37
  },
38
38
  }))
39
39
 
40
- type AdapterTypeOptions =
41
- | 'BigMafAdapter'
42
- | 'MafTabixAdapter'
43
- | 'BgzipTaffyAdapter'
40
+ type AdapterTypeOptions = 'BigMafAdapter' | 'MafTabixAdapter'
44
41
  type IndexTypeOptions = 'TBI' | 'CSI'
45
42
 
46
43
  export default function MultiMAFWidget({ model }: { model: AddTrackModel }) {
@@ -89,7 +86,8 @@ export default function MultiMAFWidget({ model }: { model: AddTrackModel }) {
89
86
  setLoc(arg)
90
87
  }}
91
88
  />
92
- ) : fileTypeChoice === 'MafTabixAdapter' ? (
89
+ ) : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
90
+ fileTypeChoice === 'MafTabixAdapter' ? (
93
91
  <>
94
92
  <FormControl>
95
93
  <FormLabel>Index type</FormLabel>
@@ -212,7 +210,8 @@ export default function MultiMAFWidget({ model }: { model: AddTrackModel }) {
212
210
  samples: sampleNames,
213
211
  nhLocation: nhLoc,
214
212
  }
215
- : fileTypeChoice === 'MafTabixAdapter'
213
+ : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
214
+ fileTypeChoice === 'MafTabixAdapter'
216
215
  ? {
217
216
  type: fileTypeChoice,
218
217
  bedGzLocation: loc,
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@ import Plugin from '@jbrowse/core/Plugin'
2
2
  import PluginManager from '@jbrowse/core/PluginManager'
3
3
 
4
4
  import { version } from '../package.json'
5
- import BgzipTaffyAdapterF from './BgzipTaffyAdapter'
6
5
  import BigMafAdapterF from './BigMafAdapter'
7
6
  import LinearMafDisplayF from './LinearMafDisplay'
8
7
  import LinearMafRendererF from './LinearMafRenderer'
@@ -22,7 +21,6 @@ export default class MafViewerPlugin extends Plugin {
22
21
  LinearMafDisplayF(pluginManager)
23
22
  LinearMafRendererF(pluginManager)
24
23
  MafTabixAdapterF(pluginManager)
25
- BgzipTaffyAdapterF(pluginManager)
26
24
  MafAddTrackWorkflowF(pluginManager)
27
25
  MafGetSequencesF(pluginManager)
28
26
  MafGetSamplesF(pluginManager)
@@ -1,307 +0,0 @@
1
- import { unzip } from '@gmod/bgzf-filehandle'
2
- import {
3
- BaseFeatureDataAdapter,
4
- BaseOptions,
5
- } from '@jbrowse/core/data_adapters/BaseAdapter'
6
- import {
7
- Feature,
8
- Region,
9
- SimpleFeature,
10
- updateStatus,
11
- } from '@jbrowse/core/util'
12
- import QuickLRU from '@jbrowse/core/util/QuickLRU'
13
- import { openLocation } from '@jbrowse/core/util/io'
14
- import { ObservableCreate } from '@jbrowse/core/util/rxjs'
15
- import AbortablePromiseCache from 'abortable-promise-cache'
16
- import Long from 'long'
17
-
18
- import VirtualOffset from './virtualOffset'
19
- import parseNewick from '../parseNewick'
20
- import { normalize } from '../util'
21
- import { parseRowInstructions } from './rowInstructions'
22
- import { parseLineByLine } from './util'
23
-
24
- import type { IndexData, OrganismRecord } from './types'
25
-
26
- interface Entry {
27
- type: string
28
- row: number
29
- asm: string
30
- ref: string
31
- start: number
32
- strand: number
33
- length: number
34
- }
35
-
36
- const toP = (s = 0) => +(+s).toFixed(1)
37
-
38
- export default class BgzipTaffyAdapter extends BaseFeatureDataAdapter {
39
- public setupP?: Promise<IndexData>
40
-
41
- private cache = new AbortablePromiseCache({
42
- cache: new QuickLRU({ maxSize: 50 }),
43
- // @ts-expect-error
44
- fill: async ({ nextEntry, firstEntry }, signal, statusCallback) => {
45
- const file = openLocation(this.getConf('tafGzLocation'))
46
- const response = await file.read(
47
- nextEntry.virtualOffset.blockPosition -
48
- firstEntry.virtualOffset.blockPosition,
49
- firstEntry.virtualOffset.blockPosition,
50
- )
51
- const buffer = await unzip(response)
52
- const slice = buffer.slice(firstEntry.virtualOffset.dataPosition)
53
- return this.getChunk(slice, {
54
- statusCallback: statusCallback as (arg: string) => void,
55
- signal,
56
- })
57
- },
58
- })
59
-
60
- async getRefNames() {
61
- const data = await this.setup()
62
- return Object.keys(data)
63
- }
64
-
65
- async getChunk(buffer: Uint8Array, opts?: BaseOptions) {
66
- const { statusCallback = () => {} } = opts || {}
67
- const alignments = {} as Record<string, OrganismRecord>
68
- const data = [] as Entry[]
69
- let a0: any
70
- let j = 0
71
- let b = 0
72
- parseLineByLine(buffer, line => {
73
- if (j++ % 100 === 0) {
74
- statusCallback(
75
- `Processing ${toP(b / 1_000_000)}/${toP(buffer.length / 1_000_000)}Mb`,
76
- )
77
- }
78
- b += line.length
79
- if (line) {
80
- const [lineData, rowInstructions] = line.split(' ; ')
81
- if (rowInstructions) {
82
- for (const ins of parseRowInstructions(rowInstructions)) {
83
- if (ins.type === 'i') {
84
- data.splice(ins.row, 0, ins)
85
- if (!alignments[ins.asm]) {
86
- alignments[ins.asm] = {
87
- start: ins.start,
88
- strand: ins.strand,
89
- srcSize: ins.length,
90
- chr: ins.ref,
91
- data: '',
92
- }
93
- }
94
- const e = alignments[ins.asm]!
95
- e.data += ' '.repeat(Math.max(0, j - e.data.length - 1)) // catch it up
96
- } else if (ins.type === 's') {
97
- if (!alignments[ins.asm]) {
98
- alignments[ins.asm] = {
99
- start: ins.start,
100
- strand: ins.strand,
101
- srcSize: ins.length,
102
- chr: ins.ref,
103
- data: '',
104
- }
105
- }
106
- const e = alignments[ins.asm]!
107
- e.data += ' '.repeat(Math.max(0, j - e.data.length - 1)) // catch it up
108
- data[ins.row] = ins
109
- } else if (ins.type === 'd') {
110
- data.splice(ins.row, 1)
111
- }
112
-
113
- // no gaps for now(?)
114
- // else if (ins.type === 'g') {
115
- // console.log('g??')
116
- // } else if (ins.type === 'G') {
117
- // console.log('G??')
118
- // }
119
- }
120
- if (!a0) {
121
- a0 = data[0]
122
- }
123
- }
124
- const lineLen = lineData!.length
125
-
126
- for (let i = 0; i < lineLen; i++) {
127
- const letter = lineData![i]
128
- const r = data[i]
129
-
130
- if (r) {
131
- alignments[r.asm]!.data += letter
132
- } else {
133
- // not sure why but chr22_KI270731v1_random.taf.gz ends up here
134
- }
135
- }
136
- }
137
- })
138
- if (a0) {
139
- const row0 = alignments[a0.asm]!
140
-
141
- // see
142
- // https://github.com/ComparativeGenomicsToolkit/taffy/blob/f5a5354/docs/taffy_utilities.md#referenced-based-maftaf-and-indexing
143
- // for the significance of row[0]:
144
- //
145
- // "An anchor line in TAF is a column from which all sequence
146
- // coordinates can be deduced without scanning backwards to previous
147
- // lines "
148
- return {
149
- uniqueId: `${row0.start}-${row0.data.length}`,
150
- start: row0.start,
151
- end: row0.start + row0.data.length,
152
- strand: row0.strand,
153
- alignments,
154
- seq: row0.data,
155
- }
156
- }
157
- return undefined
158
- }
159
-
160
- setupPre() {
161
- if (!this.setupP) {
162
- this.setupP = this.readTaiFile().catch((e: unknown) => {
163
- this.setupP = undefined
164
- throw e
165
- })
166
- }
167
- return this.setupP
168
- }
169
- setup(opts?: BaseOptions) {
170
- const { statusCallback = () => {} } = opts || {}
171
- return updateStatus('Downloading index', statusCallback, () =>
172
- this.setupPre(),
173
- )
174
- }
175
-
176
- async readTaiFile() {
177
- const text = await openLocation(this.getConf('taiLocation')).readFile(
178
- 'utf8',
179
- )
180
- const lines = text
181
- .split('\n')
182
- .map(f => f.trim())
183
- .filter(line => !!line)
184
- const entries = {} as IndexData
185
- let lastChr = ''
186
- let lastChrStart = 0
187
- let lastRawVirtualOffset = 0
188
- for (const line of lines) {
189
- const [chr, chrStart, virtualOffset] = line.split('\t')
190
- const relativizedVirtualOffset = lastRawVirtualOffset + +virtualOffset!
191
- const currChr = chr === '*' ? lastChr : chr!.split('.').at(-1)!
192
-
193
- // bgzip TAF files store virtual offsets in plaintext in the TAI file
194
- // these virtualoffsets are 64bit values, so the long library is needed
195
- // to accurately do the bit manipulations needed
196
- const x = Long.fromNumber(relativizedVirtualOffset)
197
- const y = x.shiftRightUnsigned(16)
198
- const z = x.and(0xffff)
199
- const voff = new VirtualOffset(y.toNumber(), z.toNumber())
200
-
201
- if (!entries[currChr]) {
202
- entries[currChr] = []
203
- lastChr = ''
204
- lastChrStart = 0
205
- lastRawVirtualOffset = 0
206
- }
207
- const currStart = +chrStart! + lastChrStart
208
- entries[currChr].push({
209
- chrStart: currStart,
210
- virtualOffset: voff,
211
- })
212
- lastChr = currChr
213
- lastChrStart = currStart
214
- lastRawVirtualOffset = relativizedVirtualOffset
215
- }
216
- return entries
217
- }
218
-
219
- getFeatures(query: Region, opts?: BaseOptions) {
220
- const { statusCallback = () => {} } = opts || {}
221
- return ObservableCreate<Feature>(async observer => {
222
- try {
223
- const byteRanges = await this.setup()
224
- const feat = await updateStatus(
225
- 'Downloading alignments',
226
- statusCallback,
227
- () => this.getLines(query, byteRanges),
228
- )
229
- if (feat) {
230
- observer.next(
231
- // @ts-expect-error
232
- new SimpleFeature({
233
- ...feat,
234
- refName: query.refName,
235
- }),
236
- )
237
- } else {
238
- console.error('no feature found')
239
- }
240
-
241
- statusCallback('')
242
- observer.complete()
243
- } catch (e) {
244
- observer.error(e)
245
- }
246
- })
247
- }
248
-
249
- async getSamples(_query: Region) {
250
- const nhLoc = this.getConf('nhLocation')
251
- const nh =
252
- nhLoc.uri === '/path/to/my.nh'
253
- ? undefined
254
- : await openLocation(nhLoc).readFile('utf8')
255
-
256
- // TODO: we may need to resolve the exact set of rows in the visible region
257
- // here
258
- return {
259
- samples: normalize(this.getConf('samples')),
260
- tree: nh ? parseNewick(nh) : undefined,
261
- }
262
- }
263
-
264
- // TODO: cache processed large chunks
265
- async getLines(query: Region, byteRanges: IndexData) {
266
- const records = byteRanges[query.refName]
267
- if (records) {
268
- let firstEntry
269
- let nextEntry
270
-
271
- // two pass:
272
- // first pass: find first block greater than query start, then -1 from
273
- // that
274
- for (let i = 0; i < records.length; i++) {
275
- if (records[i]!.chrStart >= query.start) {
276
- firstEntry = records[Math.max(i - 1, 0)]
277
- break
278
- }
279
- }
280
- // second pass: find first block where query end less than record start,
281
- // and +1 from that
282
- for (let i = 0; i < records.length; i++) {
283
- if (query.end <= records[i]!.chrStart) {
284
- nextEntry = records[i + 1]
285
- break
286
- }
287
- }
288
-
289
- nextEntry = nextEntry ?? records.at(-1)
290
- // we NEED at least a firstEntry (validate behavior?) because othrwise it fetches whole
291
- // file whn you request e.g. out of range region (e.g. taf in chr22:1-100
292
- // and you are at chr22:200-300)
293
- if (firstEntry && nextEntry) {
294
- return this.cache.get(
295
- `${JSON.stringify(nextEntry)}_${JSON.stringify(firstEntry)}`,
296
- {
297
- nextEntry,
298
- firstEntry,
299
- },
300
- )
301
- }
302
- }
303
- return undefined
304
- }
305
-
306
- freeResources(): void {}
307
- }
@@ -1,59 +0,0 @@
1
- import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
-
3
- /**
4
- * #config BgzipTaffyAdapter
5
- * used to configure BgzipTaffy adapter
6
- */
7
- function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
8
-
9
- const configSchema = ConfigurationSchema(
10
- 'BgzipTaffyAdapter',
11
- {
12
- /**
13
- * #slot
14
- */
15
- samples: {
16
- type: 'frozen',
17
- description: 'string[] or {id:string,label:string,color?:string}[]',
18
- defaultValue: [],
19
- },
20
- /**
21
- * #slot
22
- */
23
- tafGzLocation: {
24
- type: 'fileLocation',
25
- description: 'bgzip taffy file',
26
- defaultValue: {
27
- uri: '/path/to/my.taf',
28
- locationType: 'UriLocation',
29
- },
30
- },
31
- /**
32
- * #slot
33
- */
34
- taiLocation: {
35
- type: 'fileLocation',
36
- description: 'taffy index',
37
- defaultValue: {
38
- uri: '/path/to/my.taf.gz.tai',
39
- locationType: 'UriLocation',
40
- },
41
- },
42
- /**
43
- * #slot
44
- */
45
- nhLocation: {
46
- type: 'fileLocation',
47
- description: 'newick tree',
48
- defaultValue: {
49
- uri: '/path/to/my.nh',
50
- locationType: 'UriLocation',
51
- },
52
- },
53
- },
54
- {
55
- explicitlyTyped: true,
56
- },
57
- )
58
-
59
- export default configSchema
@@ -1,16 +0,0 @@
1
- import PluginManager from '@jbrowse/core/PluginManager'
2
- import { AdapterType } from '@jbrowse/core/pluggableElementTypes'
3
-
4
- import BgzipTaffyAdapter from './BgzipTaffyAdapter'
5
- import configSchema from './configSchema'
6
-
7
- export default function BgzipTaffyAdapterF(pluginManager: PluginManager) {
8
- return pluginManager.addAdapterType(
9
- () =>
10
- new AdapterType({
11
- name: 'BgzipTaffyAdapter',
12
- AdapterClass: BgzipTaffyAdapter,
13
- configSchema,
14
- }),
15
- )
16
- }
@@ -1,91 +0,0 @@
1
- interface RowInsert {
2
- type: 'i'
3
- row: number
4
- asm: string
5
- ref: string
6
- start: number
7
- strand: number
8
- length: number
9
- }
10
- interface RowSubstitute {
11
- type: 's'
12
- row: number
13
- asm: string
14
- ref: string
15
- start: number
16
- strand: number
17
- length: number
18
- }
19
- interface RowDelete {
20
- type: 'd'
21
- row: number
22
- }
23
- interface RowGap {
24
- type: 'g'
25
- row: number
26
- gapLen: number
27
- }
28
- interface RowGapSubstring {
29
- type: 'G'
30
- row: number
31
- gapSubstring: string
32
- }
33
- type RowInstruction =
34
- | RowInsert
35
- | RowDelete
36
- | RowGap
37
- | RowGapSubstring
38
- | RowSubstitute
39
-
40
- export function parseRowInstructions(meta: string) {
41
- const ret = meta.split(' ')
42
- const rows = [] as RowInstruction[]
43
-
44
- for (let i = 0; i < ret.length; ) {
45
- const type = ret[i++]
46
- if (type === 'i') {
47
- const row = +ret[i++]!
48
- const [asm, ref] = ret[i++]!.split('.')
49
- rows.push({
50
- type,
51
- row,
52
- asm: asm!,
53
- ref: ref!,
54
- start: +ret[i++]!,
55
- strand: ret[i++] === '-' ? -1 : 1,
56
- length: +ret[i++]!,
57
- })
58
- }
59
- if (type === 's') {
60
- const row = +ret[i++]!
61
- const [asm, ref] = ret[i++]!.split('.')
62
- rows.push({
63
- type,
64
- row,
65
- asm: asm!,
66
- ref: ref!,
67
- start: +ret[i++]!,
68
- strand: ret[i++] === '-' ? -1 : 1,
69
- length: +ret[i++]!,
70
- })
71
- } else if (type === 'd') {
72
- rows.push({
73
- type,
74
- row: +ret[i++]!,
75
- })
76
- } else if (type === 'g') {
77
- rows.push({
78
- type,
79
- row: +ret[i++]!,
80
- gapLen: +ret[i++]!,
81
- })
82
- } else if (type === 'G') {
83
- rows.push({
84
- type,
85
- row: +ret[i++]!,
86
- gapSubstring: ret[i++]!,
87
- })
88
- }
89
- }
90
- return rows
91
- }
@@ -1,16 +0,0 @@
1
- import VirtualOffset from './virtualOffset'
2
-
3
- export interface OrganismRecord {
4
- chr: string
5
- start: number
6
- srcSize: number
7
- strand: number
8
- data: string
9
- }
10
-
11
- export interface ByteRange {
12
- chrStart: number
13
- virtualOffset: VirtualOffset
14
- }
15
-
16
- export type IndexData = Record<string, ByteRange[]>
@@ -1,25 +0,0 @@
1
- export function parseLineByLine<T>(
2
- buffer: Uint8Array,
3
- cb: (line: string) => T | undefined,
4
- ): T[] {
5
- let blockStart = 0
6
- const entries: T[] = []
7
- const decoder = new TextDecoder('utf8')
8
- while (blockStart < buffer.length) {
9
- const n = buffer.indexOf(10, blockStart)
10
- if (n === -1) {
11
- break
12
- }
13
- const b = buffer.subarray(blockStart, n)
14
- const line = decoder.decode(b).trim()
15
- if (line) {
16
- const entry = cb(line)
17
- if (entry) {
18
- entries.push(entry)
19
- }
20
- }
21
-
22
- blockStart = n + 1
23
- }
24
- return entries
25
- }
@@ -1,29 +0,0 @@
1
- export default class VirtualOffset {
2
- public blockPosition: number
3
- public dataPosition: number
4
- constructor(blockPosition: number, dataPosition: number) {
5
- this.blockPosition = blockPosition // < offset of the compressed data block
6
- this.dataPosition = dataPosition // < offset into the uncompressed data
7
- }
8
-
9
- toString() {
10
- return `${this.blockPosition}:${this.dataPosition}`
11
- }
12
-
13
- compareTo(b: VirtualOffset) {
14
- return (
15
- this.blockPosition - b.blockPosition || this.dataPosition - b.dataPosition
16
- )
17
- }
18
- }
19
- export function fromBytes(bytes: Uint8Array, offset = 0) {
20
- return new VirtualOffset(
21
- bytes[offset + 7]! * 0x10000000000 +
22
- bytes[offset + 6]! * 0x100000000 +
23
- bytes[offset + 5]! * 0x1000000 +
24
- bytes[offset + 4]! * 0x10000 +
25
- bytes[offset + 3]! * 0x100 +
26
- bytes[offset + 2]!,
27
- (bytes[offset + 1]! << 8) | bytes[offset]!,
28
- )
29
- }