jbrowse-plugin-msaview 2.4.5 → 2.5.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.
package/dist/utils/msa.js CHANGED
@@ -1,23 +1,24 @@
1
1
  import { textfetch, timeout } from './fetch';
2
2
  const base = `https://www.ebi.ac.uk/Tools/services/rest`;
3
+ const email = 'colin.diesh@gmail.com';
3
4
  const algorithms = {
4
5
  clustalo: {
5
- params: { email: 'colin.diesh@gmail.com' },
6
+ params: { email },
6
7
  msaResult: 'aln-clustal_num',
7
8
  treeResult: 'phylotree',
8
9
  },
9
10
  muscle: {
10
- params: { email: 'colin.diesh@gmail.com', format: 'clw', tree: 'tree1' },
11
+ params: { email, format: 'clw', tree: 'tree1' },
11
12
  msaResult: 'fa',
12
13
  treeResult: 'phylotree',
13
14
  },
14
15
  kalign: {
15
- params: { email: 'colin.diesh@gmail.com', stype: 'protein' },
16
+ params: { email, stype: 'protein' },
16
17
  msaResult: 'fa',
17
18
  treeResult: 'phylotree',
18
19
  },
19
20
  mafft: {
20
- params: { email: 'colin.diesh@gmail.com', stype: 'protein' },
21
+ params: { email, stype: 'protein' },
21
22
  msaResult: 'fa',
22
23
  treeResult: 'phylotree',
23
24
  },
@@ -25,10 +26,6 @@ const algorithms = {
25
26
  async function wait({ onProgress, jobId, algorithm, }) {
26
27
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
27
28
  while (true) {
28
- for (let i = 0; i < 10; i++) {
29
- await timeout(1000);
30
- onProgress(`Re-checking MSA status in... ${10 - i}`);
31
- }
32
29
  const result = await textfetch(`${base}/${algorithm}/status/${jobId}`);
33
30
  if (result === 'FINISHED') {
34
31
  break;
@@ -36,6 +33,10 @@ async function wait({ onProgress, jobId, algorithm, }) {
36
33
  else if (result.includes('FAILURE')) {
37
34
  throw new Error(`Failed to run: jobId ${jobId}`);
38
35
  }
36
+ for (let i = 0; i < 10; i++) {
37
+ onProgress(`Re-checking MSA status in... ${10 - i}`);
38
+ await timeout(1000);
39
+ }
39
40
  }
40
41
  }
41
42
  export async function launchMSA({ algorithm, sequence, onProgress, }) {
@@ -58,16 +58,16 @@ async function initialQuery({ query, blastProgram, blastDatabase, baseUrl, }) {
58
58
  async function waitForRid({ rid, onProgress, baseUrl, }) {
59
59
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
60
60
  while (true) {
61
- const iter = 20;
62
- for (let i = 0; i < iter; i++) {
63
- await timeout(1000);
64
- onProgress(`Re-checking BLAST status in... ${iter - i}`);
65
- }
66
61
  const res = await textfetch(`${baseUrl}?CMD=Get&FORMAT_OBJECT=SearchInfo&RID=${rid}`);
67
62
  const statusMatch = /\s+Status=(\S+)/m.exec(res);
68
63
  const status = statusMatch?.[1];
69
64
  const hasHits = /\s+ThereAreHits=yes/m.test(res);
70
65
  if (status === 'WAITING') {
66
+ const iter = 20;
67
+ for (let i = 0; i < iter; i++) {
68
+ onProgress(`Re-checking BLAST status in... ${iter - i}`);
69
+ await timeout(1000);
70
+ }
71
71
  continue;
72
72
  }
73
73
  if (status === 'FAILED') {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.4.5";
1
+ export declare const version = "2.5.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.4.5';
1
+ export const version = '2.5.0';
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.4.5",
2
+ "version": "2.5.0",
3
3
  "license": "MIT",
4
4
  "name": "jbrowse-plugin-msaview",
5
5
  "repository": {
@@ -20,7 +20,7 @@
20
20
  "g2p_mapper": "^2.1.5",
21
21
  "idb": "^8.0.3",
22
22
  "pako-esm2": "^2.0.2",
23
- "react-msaview": "^5.0.16",
23
+ "react-msaview": "^5.1.1",
24
24
  "swr": "^2.4.1"
25
25
  },
26
26
  "devDependencies": {
@@ -28,7 +28,7 @@
28
28
  "@eslint/js": "^10.0.1",
29
29
  "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
30
30
  "@jbrowse/core": "^4.3.0",
31
- "@jbrowse/mobx-state-tree": "^5.8.7",
31
+ "@jbrowse/mobx-state-tree": "^5.10.2",
32
32
  "@jbrowse/plugin-linear-genome-view": "^4.3.0",
33
33
  "@mui/icons-material": "^7.3.11",
34
34
  "@mui/material": "^7.3.11",
@@ -36,19 +36,19 @@
36
36
  "@mui/x-data-grid": "^8.28.7",
37
37
  "@types/node": "^25.9.1",
38
38
  "@types/react": "^19.2.15",
39
- "@typescript-eslint/eslint-plugin": "^8.59.4",
40
- "@typescript-eslint/parser": "^8.59.4",
39
+ "@typescript-eslint/eslint-plugin": "^8.60.0",
40
+ "@typescript-eslint/parser": "^8.60.0",
41
41
  "esbuild": "^0.28.0",
42
42
  "eslint": "^10.4.0",
43
43
  "eslint-plugin-import-x": "^4.16.2",
44
44
  "eslint-plugin-react": "^7.37.5",
45
45
  "eslint-plugin-react-hooks": "^7.1.1",
46
46
  "eslint-plugin-unicorn": "^64.0.0",
47
- "mobx": "^6.15.3",
48
- "mobx-react": "^9.2.1",
47
+ "mobx": "^6.15.4",
48
+ "mobx-react": "^9.2.2",
49
49
  "prettier": "^3.8.3",
50
50
  "pretty-bytes": "^7.1.0",
51
- "puppeteer": "^24.43.1",
51
+ "puppeteer": "^25.1.0",
52
52
  "react": "^19.2.6",
53
53
  "react-dom": "^19.2.6",
54
54
  "rimraf": "^6.1.3",
@@ -56,7 +56,7 @@
56
56
  "serve": "^14.2.6",
57
57
  "tss-react": "^4.9.21",
58
58
  "typescript": "^6.0.3",
59
- "typescript-eslint": "^8.59.4",
59
+ "typescript-eslint": "^8.60.0",
60
60
  "vitest": "^4.1.7"
61
61
  },
62
62
  "scripts": {
@@ -40,9 +40,7 @@ export function getTranscriptLength(feature: Feature) {
40
40
  const cdsLen = sum(
41
41
  feature
42
42
  .get('subfeatures')
43
- ?.filter(
44
- f => (f.get('type') as string | undefined)?.toLowerCase() === 'cds',
45
- )
43
+ ?.filter(f => f.get('type') === 'CDS')
46
44
  .map(s => s.get('end') - s.get('start')) ?? [],
47
45
  )
48
46
  return {
@@ -1,6 +1,7 @@
1
1
  import { getSession } from '@jbrowse/core/util'
2
2
 
3
3
  import { doLaunchBlast } from './doLaunchBlast'
4
+ import { genomeToMSA } from './genomeToMSA'
4
5
  import {
5
6
  cleanupOldData,
6
7
  generateDataStoreId,
@@ -145,6 +146,27 @@ export function processInit(self: JBrowsePluginMsaViewModel) {
145
146
  }
146
147
  }
147
148
 
149
+ /**
150
+ * Mirror the connected genome view's hover position onto the MSA's hovered
151
+ * column. Returns the autorun body so it can keep a flag tracking whether the
152
+ * MSA's mouseCol was set by this sync: that way an unrelated session hover
153
+ * change clears the column only when the genome put it there, never wiping a
154
+ * column the user is hovering directly in the MSA.
155
+ */
156
+ export function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewModel) {
157
+ let genomeDrivenCol = false
158
+ return () => {
159
+ const col = genomeToMSA({ model: self })
160
+ if (col !== undefined) {
161
+ self.setMousePos(col)
162
+ genomeDrivenCol = true
163
+ } else if (genomeDrivenCol) {
164
+ self.setMousePos(undefined)
165
+ genomeDrivenCol = false
166
+ }
167
+ }
168
+ }
169
+
148
170
  export function highlightConnectedStructures(self: JBrowsePluginMsaViewModel) {
149
171
  const { mouseCol, connectedProteinViews } = self
150
172
  if (connectedProteinViews.length === 0) {
@@ -16,14 +16,11 @@ import {
16
16
  processInit,
17
17
  runCleanup,
18
18
  storeDataToIndexedDB,
19
+ syncGenomeHoverToMsaColumn,
19
20
  } from './afterCreateAutoruns'
20
- import { genomeToMSA } from './genomeToMSA'
21
21
  import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord'
22
22
  import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment'
23
- import {
24
- getProteinViews,
25
- ungappedToGappedPosition,
26
- } from './structureConnection'
23
+ import { getProteinViews } from './structureConnection'
27
24
  import { getCanonicalRefName } from './util'
28
25
 
29
26
  import type { ProteinView, StructureConnection } from './structureConnection'
@@ -209,41 +206,6 @@ export default function stateModelFactory() {
209
206
  }))
210
207
 
211
208
  .views(self => ({
212
- /**
213
- * #getter
214
- */
215
- get structureHoverCol(): number | undefined {
216
- for (const conn of self.connectedProteinViews) {
217
- const structure = conn.proteinView.structures[conn.structureIdx]
218
- const structurePos = structure?.hoverPosition?.structureSeqPos
219
- if (structurePos !== undefined) {
220
- const msaUngapped = conn.structureToMsa[structurePos]
221
- if (msaUngapped !== undefined) {
222
- const seq = self.getSequenceByRowName(conn.msaRowName)
223
- if (seq) {
224
- const globalCol = ungappedToGappedPosition(seq, msaUngapped)
225
- if (globalCol !== undefined) {
226
- return self.globalColToVisibleCol(globalCol)
227
- }
228
- }
229
- }
230
- }
231
- }
232
- return undefined
233
- },
234
- }))
235
-
236
- .views(self => ({
237
- /**
238
- * #getter
239
- */
240
- get mouseCol2(): number | undefined {
241
- return (
242
- self.structureHoverCol ??
243
- genomeToMSA({ model: self as JBrowsePluginMsaViewModel })
244
- )
245
- },
246
-
247
209
  /**
248
210
  * #getter
249
211
  */
@@ -394,14 +356,13 @@ export default function stateModelFactory() {
394
356
  ungappedMsaSequence,
395
357
  structureSequence,
396
358
  )
397
- const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment)
359
+ const { seq1ToSeq2 } = buildAlignmentMaps(alignment)
398
360
 
399
361
  const connection: StructureConnection = {
400
362
  proteinViewId,
401
363
  structureIdx,
402
364
  msaRowName: rowName,
403
365
  msaToStructure: Object.fromEntries(seq1ToSeq2),
404
- structureToMsa: Object.fromEntries(seq2ToSeq1),
405
366
  }
406
367
 
407
368
  self.connectedStructures.push(connection)
@@ -503,6 +464,7 @@ export default function stateModelFactory() {
503
464
  }),
504
465
  )
505
466
  }
467
+ addDisposer(self, autorun(syncGenomeHoverToMsaColumn(self)))
506
468
  },
507
469
  }))
508
470
  }
@@ -128,13 +128,8 @@ function buildConsensus(alignedSeq1: string, alignedSeq2: string) {
128
128
  for (let i = 0; i < alignedSeq1.length; i++) {
129
129
  const a = alignedSeq1[i]!
130
130
  const b = alignedSeq2[i]!
131
- if (a === '-' || b === '-') {
132
- consensus += ' '
133
- } else if (a.toUpperCase() === b.toUpperCase()) {
134
- consensus += '|'
135
- } else {
136
- consensus += ' '
137
- }
131
+ const match = a !== '-' && b !== '-' && a.toUpperCase() === b.toUpperCase()
132
+ consensus += match ? '|' : ' '
138
133
  }
139
134
  return consensus
140
135
  }
@@ -1,9 +1,6 @@
1
1
  import { describe, expect, test } from 'vitest'
2
2
 
3
- import {
4
- gappedToUngappedPosition,
5
- ungappedToGappedPosition,
6
- } from './structureConnection'
3
+ import { gappedToUngappedPosition } from './structureConnection'
7
4
 
8
5
  describe('gappedToUngappedPosition', () => {
9
6
  test('returns correct ungapped position for non-gap character', () => {
@@ -63,60 +60,3 @@ describe('gappedToUngappedPosition', () => {
63
60
  expect(gappedToUngappedPosition(seq, 2)).toBeUndefined()
64
61
  })
65
62
  })
66
-
67
- describe('ungappedToGappedPosition', () => {
68
- test('returns correct gapped position', () => {
69
- const seq = 'M-KA-A'
70
- // 0 12 34 (gapped)
71
- // 0 1 23 (ungapped)
72
- expect(ungappedToGappedPosition(seq, 0)).toBe(0) // M
73
- expect(ungappedToGappedPosition(seq, 1)).toBe(2) // K
74
- expect(ungappedToGappedPosition(seq, 2)).toBe(3) // A
75
- expect(ungappedToGappedPosition(seq, 3)).toBe(5) // A
76
- })
77
-
78
- test('returns undefined for out-of-bounds ungapped position', () => {
79
- const seq = 'M-KA'
80
- expect(ungappedToGappedPosition(seq, 4)).toBeUndefined()
81
- expect(ungappedToGappedPosition(seq, 100)).toBeUndefined()
82
- })
83
-
84
- test('handles sequence with no gaps', () => {
85
- const seq = 'MKAA'
86
- expect(ungappedToGappedPosition(seq, 0)).toBe(0)
87
- expect(ungappedToGappedPosition(seq, 1)).toBe(1)
88
- expect(ungappedToGappedPosition(seq, 2)).toBe(2)
89
- expect(ungappedToGappedPosition(seq, 3)).toBe(3)
90
- })
91
-
92
- test('handles sequence with leading gaps', () => {
93
- const seq = '--MKA'
94
- expect(ungappedToGappedPosition(seq, 0)).toBe(2) // M
95
- expect(ungappedToGappedPosition(seq, 1)).toBe(3) // K
96
- expect(ungappedToGappedPosition(seq, 2)).toBe(4) // A
97
- })
98
-
99
- test('handles empty sequence', () => {
100
- expect(ungappedToGappedPosition('', 0)).toBeUndefined()
101
- })
102
-
103
- test('handles all-gap sequence', () => {
104
- const seq = '---'
105
- expect(ungappedToGappedPosition(seq, 0)).toBeUndefined()
106
- })
107
- })
108
-
109
- describe('gappedToUngappedPosition and ungappedToGappedPosition are inverses', () => {
110
- test('round-trip conversion works', () => {
111
- const seq = 'M-KA--YL-S'
112
- // For each non-gap position, converting to ungapped and back should return original
113
- for (let i = 0; i < seq.length; i++) {
114
- if (seq[i] !== '-') {
115
- const ungapped = gappedToUngappedPosition(seq, i)
116
- expect(ungapped).toBeDefined()
117
- const backToGapped = ungappedToGappedPosition(seq, ungapped!)
118
- expect(backToGapped).toBe(i)
119
- }
120
- }
121
- })
122
- })
@@ -40,8 +40,6 @@ export interface StructureConnection {
40
40
  msaRowName: string
41
41
  /** Map from MSA ungapped position to structure sequence position */
42
42
  msaToStructure: Record<number, number>
43
- /** Map from structure sequence position to MSA ungapped position */
44
- structureToMsa: Record<number, number>
45
43
  }
46
44
 
47
45
  /**
@@ -69,23 +67,3 @@ export function gappedToUngappedPosition(
69
67
 
70
68
  return ungapped
71
69
  }
72
-
73
- /**
74
- * Helper to convert ungapped position to gapped MSA column for a specific row
75
- */
76
- export function ungappedToGappedPosition(
77
- sequence: string,
78
- ungappedPosition: number,
79
- ): number | undefined {
80
- let ungapped = 0
81
- for (let i = 0; i < sequence.length; i++) {
82
- const element = sequence[i]
83
- if (element !== '-') {
84
- if (ungapped === ungappedPosition) {
85
- return i
86
- }
87
- ungapped++
88
- }
89
- }
90
- return undefined
91
- }
@@ -0,0 +1,112 @@
1
+ import { getSession } from '@jbrowse/core/util'
2
+ import { beforeEach, describe, expect, test, vi } from 'vitest'
3
+
4
+ import { syncGenomeHoverToMsaColumn } from './afterCreateAutoruns'
5
+
6
+ import type { JBrowsePluginMsaViewModel } from './model'
7
+
8
+ // Mock only getSession; keep the rest of the util module real so the
9
+ // afterCreateAutoruns import graph still loads.
10
+ vi.mock('@jbrowse/core/util', async importOriginal => ({
11
+ ...(await importOriginal<Record<string, unknown>>()),
12
+ getSession: vi.fn(),
13
+ }))
14
+
15
+ const mockGetSession = vi.mocked(getSession)
16
+
17
+ const mafRegion = {
18
+ refName: 'chr1',
19
+ start: 1000,
20
+ end: 1010,
21
+ assemblyName: 'hg38',
22
+ }
23
+
24
+ // A model wired through the real genomeToMSA path: a connected genome view
25
+ // over a maf region, with seqPosToVisibleCol as identity so the asserted
26
+ // column equals the ungapped offset into the region.
27
+ function makeModel() {
28
+ const calls: (number | undefined)[] = []
29
+ const model = {
30
+ querySeqName: 'hg38.chr1',
31
+ transcriptToMsaMap: undefined,
32
+ mafRegion,
33
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
34
+ seqPosToVisibleCol: (_name: string, pos: number) => pos,
35
+ setMousePos: (col?: number) => {
36
+ calls.push(col)
37
+ },
38
+ } as unknown as JBrowsePluginMsaViewModel
39
+ return { model, calls }
40
+ }
41
+
42
+ function hoverGenome(coord: number) {
43
+ mockGetSession.mockReturnValue({
44
+ hovered: { hoverFeature: {}, hoverPosition: { coord, refName: 'chr1' } },
45
+ } as unknown as ReturnType<typeof getSession>)
46
+ }
47
+
48
+ function clearGenomeHover() {
49
+ mockGetSession.mockReturnValue({
50
+ hovered: null,
51
+ } as unknown as ReturnType<typeof getSession>)
52
+ }
53
+
54
+ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
55
+ beforeEach(() => {
56
+ vi.clearAllMocks()
57
+ })
58
+
59
+ test('genome hover at coord 1005 highlights MSA column 5', () => {
60
+ const { model, calls } = makeModel()
61
+ const run = syncGenomeHoverToMsaColumn(model)
62
+
63
+ hoverGenome(1005) // 1005 - mafRegion.start(1000) = ungapped 5
64
+ run()
65
+ expect(calls).toEqual([5])
66
+ })
67
+
68
+ test('moving the genome hover moves the highlighted column', () => {
69
+ const { model, calls } = makeModel()
70
+ const run = syncGenomeHoverToMsaColumn(model)
71
+
72
+ hoverGenome(1002)
73
+ run()
74
+ hoverGenome(1007)
75
+ run()
76
+ expect(calls).toEqual([2, 7])
77
+ })
78
+
79
+ test('leaving the genome clears the column it set', () => {
80
+ const { model, calls } = makeModel()
81
+ const run = syncGenomeHoverToMsaColumn(model)
82
+
83
+ hoverGenome(1004)
84
+ run()
85
+ clearGenomeHover()
86
+ run()
87
+ expect(calls).toEqual([4, undefined])
88
+ })
89
+
90
+ test('a hover outside the maf region clears a previously-set column once', () => {
91
+ const { model, calls } = makeModel()
92
+ const run = syncGenomeHoverToMsaColumn(model)
93
+
94
+ hoverGenome(1004)
95
+ run()
96
+ hoverGenome(5000) // outside [1000,1010) -> genomeToMSA returns undefined
97
+ run()
98
+ run()
99
+ expect(calls).toEqual([4, undefined])
100
+ })
101
+
102
+ test('never touches mouseCol when the genome never provides a column, so a direct MSA hover survives unrelated session hovers', () => {
103
+ const { model, calls } = makeModel()
104
+ const run = syncGenomeHoverToMsaColumn(model)
105
+
106
+ clearGenomeHover()
107
+ run()
108
+ hoverGenome(9999) // unrelated/out-of-range hover elsewhere
109
+ run()
110
+ expect(calls).toEqual([])
111
+ })
112
+ })
package/src/utils/msa.ts CHANGED
@@ -3,6 +3,7 @@ import { textfetch, timeout } from './fetch'
3
3
  import type { MsaAlgorithm } from '../LaunchMsaView/components/NCBIBlastQuery/consts'
4
4
 
5
5
  const base = `https://www.ebi.ac.uk/Tools/services/rest`
6
+ const email = 'colin.diesh@gmail.com'
6
7
 
7
8
  const algorithms: Record<
8
9
  MsaAlgorithm,
@@ -13,22 +14,22 @@ const algorithms: Record<
13
14
  }
14
15
  > = {
15
16
  clustalo: {
16
- params: { email: 'colin.diesh@gmail.com' },
17
+ params: { email },
17
18
  msaResult: 'aln-clustal_num',
18
19
  treeResult: 'phylotree',
19
20
  },
20
21
  muscle: {
21
- params: { email: 'colin.diesh@gmail.com', format: 'clw', tree: 'tree1' },
22
+ params: { email, format: 'clw', tree: 'tree1' },
22
23
  msaResult: 'fa',
23
24
  treeResult: 'phylotree',
24
25
  },
25
26
  kalign: {
26
- params: { email: 'colin.diesh@gmail.com', stype: 'protein' },
27
+ params: { email, stype: 'protein' },
27
28
  msaResult: 'fa',
28
29
  treeResult: 'phylotree',
29
30
  },
30
31
  mafft: {
31
- params: { email: 'colin.diesh@gmail.com', stype: 'protein' },
32
+ params: { email, stype: 'protein' },
32
33
  msaResult: 'fa',
33
34
  treeResult: 'phylotree',
34
35
  },
@@ -45,10 +46,6 @@ async function wait({
45
46
  }) {
46
47
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
47
48
  while (true) {
48
- for (let i = 0; i < 10; i++) {
49
- await timeout(1000)
50
- onProgress(`Re-checking MSA status in... ${10 - i}`)
51
- }
52
49
  const result = await textfetch(`${base}/${algorithm}/status/${jobId}`)
53
50
 
54
51
  if (result === 'FINISHED') {
@@ -56,6 +53,11 @@ async function wait({
56
53
  } else if (result.includes('FAILURE')) {
57
54
  throw new Error(`Failed to run: jobId ${jobId}`)
58
55
  }
56
+
57
+ for (let i = 0; i < 10; i++) {
58
+ onProgress(`Re-checking MSA status in... ${10 - i}`)
59
+ await timeout(1000)
60
+ }
59
61
  }
60
62
  }
61
63
 
@@ -112,12 +112,6 @@ async function waitForRid({
112
112
  }) {
113
113
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
114
114
  while (true) {
115
- const iter = 20
116
- for (let i = 0; i < iter; i++) {
117
- await timeout(1000)
118
- onProgress(`Re-checking BLAST status in... ${iter - i}`)
119
- }
120
-
121
115
  const res = await textfetch(
122
116
  `${baseUrl}?CMD=Get&FORMAT_OBJECT=SearchInfo&RID=${rid}`,
123
117
  )
@@ -126,6 +120,11 @@ async function waitForRid({
126
120
  const hasHits = /\s+ThereAreHits=yes/m.test(res)
127
121
 
128
122
  if (status === 'WAITING') {
123
+ const iter = 20
124
+ for (let i = 0; i < iter; i++) {
125
+ onProgress(`Re-checking BLAST status in... ${iter - i}`)
126
+ await timeout(1000)
127
+ }
129
128
  continue
130
129
  }
131
130
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '2.4.5'
1
+ export const version = '2.5.0'