jbrowse-plugin-msaview 3.3.0 → 3.4.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/dist/AddHighlightModel/GenomeMouseoverHighlight.js +1 -1
- package/dist/AddHighlightModel/MsaToGenomeHighlight.js +1 -1
- package/dist/AddHighlightModel/index.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +60 -18
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +2 -2
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.d.ts +12 -0
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +21 -2
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.test.js +29 -0
- package/dist/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +28 -0
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +21 -0
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.d.ts +7 -0
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.js +42 -0
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.test.js +32 -0
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +1 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +5 -4
- package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +1 -1
- package/dist/LaunchMsaView/components/useFeatureSequence.js +1 -1
- package/dist/LaunchMsaView/detectQueryRow.d.ts +15 -2
- package/dist/LaunchMsaView/detectQueryRow.js +20 -21
- package/dist/LaunchMsaView/detectQueryRow.test.js +15 -15
- package/dist/LaunchMsaView/useQueryRowName.js +5 -8
- package/dist/LaunchMsaViewExtensionPoint/index.js +11 -6
- package/dist/LaunchMsaViewExtensionPoint/index.test.d.ts +1 -0
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +43 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +15 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.js +95 -72
- package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +2 -2
- package/dist/MsaViewPanel/components/JobLink.js +7 -1
- package/dist/MsaViewPanel/components/LaunchProgress.d.ts +17 -0
- package/dist/MsaViewPanel/components/LaunchProgress.js +50 -0
- package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -3
- package/dist/MsaViewPanel/components/MsaViewPanel.test.d.ts +1 -0
- package/dist/MsaViewPanel/components/MsaViewPanel.test.js +87 -0
- package/dist/MsaViewPanel/doLaunchBlast.d.ts +4 -2
- package/dist/MsaViewPanel/doLaunchBlast.js +86 -56
- package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +3 -1
- package/dist/MsaViewPanel/doLaunchOrthologs.js +6 -5
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +39 -25
- package/dist/MsaViewPanel/genomeToMSA.js +4 -2
- package/dist/MsaViewPanel/genomeToMSA.test.js +34 -0
- package/dist/MsaViewPanel/model.d.ts +190 -19
- package/dist/MsaViewPanel/model.js +51 -1
- package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
- package/dist/MsaViewPanel/msaDataStore.js +16 -7
- package/dist/MsaViewPanel/msaDataStore.test.d.ts +1 -0
- package/dist/MsaViewPanel/msaDataStore.test.js +44 -0
- package/dist/MsaViewPanel/observeProteinHighlights.test.js +11 -0
- package/dist/MsaViewPanel/runLaunch.d.ts +38 -0
- package/dist/MsaViewPanel/runLaunch.js +65 -0
- package/dist/MsaViewPanel/runLaunch.test.d.ts +1 -0
- package/dist/MsaViewPanel/runLaunch.test.js +129 -0
- package/dist/MsaViewPanel/storedData.test.d.ts +1 -0
- package/dist/MsaViewPanel/storedData.test.js +160 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +1 -0
- package/dist/MsaViewPanel/util.d.ts +18 -0
- package/dist/MsaViewPanel/util.js +17 -0
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +47 -35
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +10 -6
- package/dist/utils/blastCache.js +47 -3
- package/dist/utils/blastCache.test.d.ts +1 -0
- package/dist/utils/blastCache.test.js +72 -0
- package/dist/utils/ebiBlast.d.ts +5 -3
- package/dist/utils/ebiBlast.js +6 -4
- package/dist/utils/ebiJobDispatcher.d.ts +6 -3
- package/dist/utils/ebiJobDispatcher.js +13 -6
- package/dist/utils/fetch.d.ts +8 -1
- package/dist/utils/fetch.js +28 -2
- package/dist/utils/msa.d.ts +15 -1
- package/dist/utils/msa.js +40 -13
- package/dist/utils/msaRows.d.ts +31 -0
- package/dist/utils/msaRows.js +67 -0
- package/dist/utils/ncbiDomains.d.ts +2 -2
- package/dist/utils/ncbiOrthologs.d.ts +11 -1
- package/dist/utils/ncbiOrthologs.js +26 -7
- package/dist/utils/ncbiOrthologs.test.js +23 -1
- package/dist/utils/pantherOrthologs.js +2 -10
- package/dist/utils/phmmer.d.ts +54 -0
- package/dist/utils/phmmer.js +120 -0
- package/dist/utils/poll.d.ts +6 -1
- package/dist/utils/poll.js +7 -2
- package/dist/utils/taxonomyNames.d.ts +1 -1
- package/dist/utils/taxonomyNames.js +6 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +30 -24
- package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +1 -1
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +1 -1
- package/src/AddHighlightModel/index.tsx +1 -1
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +85 -31
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +1 -1
- package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +4 -4
- package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.test.ts +50 -0
- package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +23 -3
- package/src/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.tsx +1 -1
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +40 -0
- package/src/LaunchMsaView/components/BlastQuery/searchChoiceStorage.test.ts +43 -0
- package/src/LaunchMsaView/components/BlastQuery/searchChoiceStorage.ts +64 -0
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +1 -1
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +7 -7
- package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +1 -1
- package/src/LaunchMsaView/components/useFeatureSequence.ts +1 -1
- package/src/LaunchMsaView/detectQueryRow.test.ts +17 -15
- package/src/LaunchMsaView/detectQueryRow.ts +34 -23
- package/src/LaunchMsaView/useQueryRowName.ts +6 -9
- package/src/LaunchMsaViewExtensionPoint/index.test.ts +51 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +21 -6
- package/src/MsaViewPanel/afterCreateAutoruns.ts +102 -68
- package/src/MsaViewPanel/components/ErrorBoundary.tsx +2 -1
- package/src/MsaViewPanel/components/JobLink.tsx +7 -2
- package/src/MsaViewPanel/components/LaunchProgress.tsx +80 -0
- package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +104 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -4
- package/src/MsaViewPanel/doLaunchBlast.ts +134 -72
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +43 -28
- package/src/MsaViewPanel/doLaunchOrthologs.ts +9 -5
- package/src/MsaViewPanel/genomeToMSA.test.ts +37 -0
- package/src/MsaViewPanel/genomeToMSA.ts +6 -2
- package/src/MsaViewPanel/model.ts +82 -6
- package/src/MsaViewPanel/msaDataStore.test.ts +54 -0
- package/src/MsaViewPanel/msaDataStore.ts +22 -13
- package/src/MsaViewPanel/observeProteinHighlights.test.ts +13 -0
- package/src/MsaViewPanel/runLaunch.test.ts +154 -0
- package/src/MsaViewPanel/runLaunch.ts +102 -0
- package/src/MsaViewPanel/storedData.test.ts +196 -0
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +1 -0
- package/src/MsaViewPanel/util.ts +18 -0
- package/src/utils/blastCache.test.ts +86 -0
- package/src/utils/blastCache.ts +67 -13
- package/src/utils/ebiBlast.ts +9 -3
- package/src/utils/ebiJobDispatcher.ts +18 -3
- package/src/utils/fetch.ts +29 -2
- package/src/utils/msa.ts +51 -12
- package/src/utils/msaRows.ts +95 -0
- package/src/utils/ncbiDomains.ts +4 -2
- package/src/utils/ncbiOrthologs.test.ts +25 -0
- package/src/utils/ncbiOrthologs.ts +27 -7
- package/src/utils/pantherOrthologs.ts +6 -11
- package/src/utils/phmmer.ts +178 -0
- package/src/utils/poll.ts +8 -1
- package/src/utils/taxonomyNames.ts +6 -1
- package/src/version.ts +1 -1
- package/dist/MsaViewPanel/components/LoadingBLAST.d.ts +0 -6
- package/dist/MsaViewPanel/components/LoadingBLAST.js +0 -26
- package/src/MsaViewPanel/components/LoadingBLAST.tsx +0 -48
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import type { BlastDatabase, MsaAlgorithm } from '../LaunchMsaView/components/BlastQuery/consts';
|
|
1
|
+
import type { BlastDatabase, MsaAlgorithm, PhmmerDatabase, SearchProgram } from '../LaunchMsaView/components/BlastQuery/consts';
|
|
2
2
|
export interface CachedBlastResult {
|
|
3
3
|
id: string;
|
|
4
4
|
proteinSequence: string;
|
|
5
|
-
blastDatabase: BlastDatabase;
|
|
5
|
+
blastDatabase: BlastDatabase | PhmmerDatabase;
|
|
6
6
|
/**
|
|
7
7
|
* Only ever set on rows cached by a version that still queried NCBI, where
|
|
8
8
|
* the choice between blastp and quick-blastp was real. Kept so those rows
|
|
9
9
|
* still display; never written now.
|
|
10
10
|
*/
|
|
11
11
|
blastProgram?: string;
|
|
12
|
-
|
|
12
|
+
/** absent on rows cached before phmmer existed, which were all blastp */
|
|
13
|
+
searchProgram?: SearchProgram;
|
|
14
|
+
/** absent on phmmer rows, which are aligned by the search itself */
|
|
15
|
+
msaAlgorithm?: MsaAlgorithm;
|
|
13
16
|
msa: string;
|
|
14
17
|
tree: string;
|
|
15
18
|
treeMetadata: string;
|
|
@@ -20,10 +23,11 @@ export interface CachedBlastResult {
|
|
|
20
23
|
transcriptName?: string;
|
|
21
24
|
geneName?: string;
|
|
22
25
|
}
|
|
23
|
-
export declare function saveBlastResult({ proteinSequence, blastDatabase, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }: {
|
|
26
|
+
export declare function saveBlastResult({ proteinSequence, blastDatabase, msaAlgorithm, searchProgram, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }: {
|
|
24
27
|
proteinSequence: string;
|
|
25
|
-
blastDatabase: BlastDatabase;
|
|
26
|
-
msaAlgorithm
|
|
28
|
+
blastDatabase: BlastDatabase | PhmmerDatabase;
|
|
29
|
+
msaAlgorithm?: MsaAlgorithm;
|
|
30
|
+
searchProgram?: SearchProgram;
|
|
27
31
|
msa: string;
|
|
28
32
|
tree: string;
|
|
29
33
|
treeMetadata: string;
|
package/dist/utils/blastCache.js
CHANGED
|
@@ -2,6 +2,12 @@ import { createDbOpener } from './idb';
|
|
|
2
2
|
const DB_NAME = 'jbrowse-msaview-blast-cache';
|
|
3
3
|
const STORE_NAME = 'blast-results';
|
|
4
4
|
const DB_VERSION = 2;
|
|
5
|
+
/**
|
|
6
|
+
* How many results the history keeps. Every row holds a whole alignment and its
|
|
7
|
+
* tree — megabytes each — so an unbounded store grows until the browser starts
|
|
8
|
+
* refusing writes to it, and the user never sees why.
|
|
9
|
+
*/
|
|
10
|
+
const MAX_CACHED_RESULTS = 50;
|
|
5
11
|
const getDB = createDbOpener(DB_NAME, DB_VERSION, (db, oldVersion) => {
|
|
6
12
|
if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
|
|
7
13
|
db.deleteObjectStore(STORE_NAME);
|
|
@@ -10,21 +16,33 @@ const getDB = createDbOpener(DB_NAME, DB_VERSION, (db, oldVersion) => {
|
|
|
10
16
|
db.createObjectStore(STORE_NAME, { keyPath: 'id' });
|
|
11
17
|
}
|
|
12
18
|
});
|
|
13
|
-
function createCacheKey(proteinSequence, blastDatabase, msaAlgorithm, transcriptId) {
|
|
19
|
+
function createCacheKey({ proteinSequence, blastDatabase, msaAlgorithm, searchProgram, transcriptId, }) {
|
|
14
20
|
const idPart = transcriptId ? `:${transcriptId}` : '';
|
|
21
|
+
// phmmer keys are prefixed and blastp keys are left exactly as they were, so
|
|
22
|
+
// results cached before phmmer existed still resolve
|
|
23
|
+
if (searchProgram === 'phmmer') {
|
|
24
|
+
return `phmmer:${blastDatabase}${idPart}:${proteinSequence}`;
|
|
25
|
+
}
|
|
15
26
|
// msaAlgorithm is part of the key because the stored msa/tree are produced by
|
|
16
27
|
// it — without it, re-running the same query under a different algorithm
|
|
17
28
|
// overwrites the earlier result and drops it from the history list
|
|
18
29
|
return `${blastDatabase}:${msaAlgorithm}${idPart}:${proteinSequence}`;
|
|
19
30
|
}
|
|
20
|
-
export async function saveBlastResult({ proteinSequence, blastDatabase, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }) {
|
|
31
|
+
export async function saveBlastResult({ proteinSequence, blastDatabase, msaAlgorithm, searchProgram, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }) {
|
|
21
32
|
const db = await getDB();
|
|
22
|
-
const id = createCacheKey(
|
|
33
|
+
const id = createCacheKey({
|
|
34
|
+
proteinSequence,
|
|
35
|
+
blastDatabase,
|
|
36
|
+
msaAlgorithm,
|
|
37
|
+
searchProgram,
|
|
38
|
+
transcriptId,
|
|
39
|
+
});
|
|
23
40
|
const entry = {
|
|
24
41
|
id,
|
|
25
42
|
proteinSequence,
|
|
26
43
|
blastDatabase,
|
|
27
44
|
msaAlgorithm,
|
|
45
|
+
searchProgram,
|
|
28
46
|
msa,
|
|
29
47
|
tree,
|
|
30
48
|
treeMetadata,
|
|
@@ -36,8 +54,34 @@ export async function saveBlastResult({ proteinSequence, blastDatabase, msaAlgor
|
|
|
36
54
|
geneName,
|
|
37
55
|
};
|
|
38
56
|
await db.put(STORE_NAME, entry);
|
|
57
|
+
await evictOldest(db);
|
|
39
58
|
return entry;
|
|
40
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Drop the oldest rows until the store is back at MAX_CACHED_RESULTS.
|
|
62
|
+
*
|
|
63
|
+
* `count` first so the common save reads no values at all: without a timestamp
|
|
64
|
+
* index the oldest have to be found by loading every row, and each one is an
|
|
65
|
+
* entire alignment. Failing to evict must not fail the save — the result is
|
|
66
|
+
* already in hand and losing it to a housekeeping error would be the worse
|
|
67
|
+
* outcome.
|
|
68
|
+
*/
|
|
69
|
+
async function evictOldest(db) {
|
|
70
|
+
try {
|
|
71
|
+
if ((await db.count(STORE_NAME)) <= MAX_CACHED_RESULTS) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const all = await db.getAll(STORE_NAME);
|
|
75
|
+
const doomed = all
|
|
76
|
+
.toSorted((a, b) => a.timestamp - b.timestamp)
|
|
77
|
+
.slice(0, all.length - MAX_CACHED_RESULTS);
|
|
78
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
79
|
+
await Promise.all([...doomed.map(e => tx.store.delete(e.id)), tx.done]);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.warn('Failed to evict old BLAST cache entries:', e);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
41
85
|
export async function getAllCachedResults() {
|
|
42
86
|
const db = await getDB();
|
|
43
87
|
const results = await db.getAll(STORE_NAME);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
|
2
|
+
import { getAllCachedResults, saveBlastResult } from './blastCache';
|
|
3
|
+
// An in-memory stand-in for the one object store this module opens. Only the
|
|
4
|
+
// idb calls blastCache makes are implemented; anything else would be untested
|
|
5
|
+
// scaffolding.
|
|
6
|
+
const { rows } = vi.hoisted(() => ({
|
|
7
|
+
rows: new Map(),
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('./idb', () => ({
|
|
10
|
+
createDbOpener: () => () => Promise.resolve({
|
|
11
|
+
put: (_store, value) => {
|
|
12
|
+
rows.set(value.id, value);
|
|
13
|
+
return Promise.resolve(value.id);
|
|
14
|
+
},
|
|
15
|
+
count: () => Promise.resolve(rows.size),
|
|
16
|
+
getAll: () => Promise.resolve([...rows.values()]),
|
|
17
|
+
transaction: () => ({
|
|
18
|
+
store: {
|
|
19
|
+
delete: (id) => {
|
|
20
|
+
rows.delete(id);
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
done: Promise.resolve(),
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
}));
|
|
28
|
+
function save(n) {
|
|
29
|
+
return saveBlastResult({
|
|
30
|
+
proteinSequence: `SEQ${n}`,
|
|
31
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
32
|
+
msaAlgorithm: 'clustalo',
|
|
33
|
+
msa: '>a\nMK',
|
|
34
|
+
tree: '(a);',
|
|
35
|
+
treeMetadata: '{}',
|
|
36
|
+
rid: `job-${n}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
rows.clear();
|
|
41
|
+
// strictly increasing, so "oldest" is unambiguous -- 55 saves in one
|
|
42
|
+
// millisecond would otherwise all carry the same timestamp
|
|
43
|
+
let clock = 1;
|
|
44
|
+
vi.spyOn(Date, 'now').mockImplementation(() => clock++);
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
});
|
|
49
|
+
test('a store at the cap loses nothing', async () => {
|
|
50
|
+
for (let i = 0; i < 50; i++) {
|
|
51
|
+
await save(i);
|
|
52
|
+
}
|
|
53
|
+
expect(rows.size).toBe(50);
|
|
54
|
+
});
|
|
55
|
+
// Every row holds a whole alignment and its tree, so an unbounded store grows
|
|
56
|
+
// until the browser refuses writes to it and the user never learns why.
|
|
57
|
+
test('going over the cap drops the oldest results and keeps the newest 50', async () => {
|
|
58
|
+
for (let i = 0; i < 55; i++) {
|
|
59
|
+
await save(i);
|
|
60
|
+
}
|
|
61
|
+
expect(rows.size).toBe(50);
|
|
62
|
+
const rids = (await getAllCachedResults()).map((r) => r.rid);
|
|
63
|
+
expect(rids.at(0)).toBe('job-54');
|
|
64
|
+
expect(rids.at(-1)).toBe('job-5');
|
|
65
|
+
});
|
|
66
|
+
test('re-saving the same query overwrites its row rather than growing the store', async () => {
|
|
67
|
+
for (let i = 0; i < 50; i++) {
|
|
68
|
+
await save(i);
|
|
69
|
+
}
|
|
70
|
+
await save(0);
|
|
71
|
+
expect(rows.size).toBe(50);
|
|
72
|
+
});
|
package/dist/utils/ebiBlast.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BlastHit } from './types';
|
|
2
1
|
import type { BlastDatabase } from '../LaunchMsaView/components/BlastQuery/consts';
|
|
2
|
+
import type { BlastHit } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* The subset of EBI's ncbiblast JSON result this plugin reads. The service
|
|
5
5
|
* returns a great deal more per hit (urls, bit scores, e-values, the match
|
|
@@ -33,18 +33,20 @@ export declare function normalizeEbiBlastHits(result: EbiBlastJson): BlastHit[];
|
|
|
33
33
|
* yet at the moment the link is on screen.
|
|
34
34
|
*/
|
|
35
35
|
export declare function ebiBlastResultUrl(jobId: string): string;
|
|
36
|
-
export declare function queryEbiBlastFromJobId({ jobId, onProgress, }: {
|
|
36
|
+
export declare function queryEbiBlastFromJobId({ jobId, onProgress, signal, }: {
|
|
37
37
|
jobId: string;
|
|
38
38
|
onProgress: (arg: string) => void;
|
|
39
|
+
signal?: AbortSignal;
|
|
39
40
|
}): Promise<{
|
|
40
41
|
rid: string;
|
|
41
42
|
hits: BlastHit[];
|
|
42
43
|
}>;
|
|
43
|
-
export declare function queryEbiBlast({ query, blastDatabase, onProgress, onRid, }: {
|
|
44
|
+
export declare function queryEbiBlast({ query, blastDatabase, onProgress, onRid, signal, }: {
|
|
44
45
|
query: string;
|
|
45
46
|
blastDatabase: BlastDatabase;
|
|
46
47
|
onProgress: (arg: string) => void;
|
|
47
48
|
onRid: (arg: string) => void;
|
|
49
|
+
signal?: AbortSignal;
|
|
48
50
|
}): Promise<{
|
|
49
51
|
rid: string;
|
|
50
52
|
hits: BlastHit[];
|
package/dist/utils/ebiBlast.js
CHANGED
|
@@ -32,22 +32,23 @@ export function normalizeEbiBlastHits(result) {
|
|
|
32
32
|
export function ebiBlastResultUrl(jobId) {
|
|
33
33
|
return `https://www.ebi.ac.uk/jdispatcher/sss/${TOOL}/summary?jobId=${jobId}`;
|
|
34
34
|
}
|
|
35
|
-
export async function queryEbiBlastFromJobId({ jobId, onProgress, }) {
|
|
35
|
+
export async function queryEbiBlastFromJobId({ jobId, onProgress, signal, }) {
|
|
36
36
|
onProgress(`Checking BLAST status for job: ${jobId}...`);
|
|
37
37
|
await waitForEbiJob({
|
|
38
38
|
tool: TOOL,
|
|
39
39
|
jobId,
|
|
40
|
+
signal,
|
|
40
41
|
onCountdown: s => {
|
|
41
42
|
onProgress(`Re-checking BLAST status in... ${s}`);
|
|
42
43
|
},
|
|
43
44
|
});
|
|
44
|
-
const hits = normalizeEbiBlastHits(JSON.parse(await fetchEbiResult({ tool: TOOL, jobId, type: 'json' })));
|
|
45
|
+
const hits = normalizeEbiBlastHits(JSON.parse(await fetchEbiResult({ tool: TOOL, jobId, type: 'json', signal })));
|
|
45
46
|
if (hits.length === 0) {
|
|
46
47
|
throw new Error('No hits found');
|
|
47
48
|
}
|
|
48
49
|
return { rid: jobId, hits };
|
|
49
50
|
}
|
|
50
|
-
export async function queryEbiBlast({ query, blastDatabase, onProgress, onRid, }) {
|
|
51
|
+
export async function queryEbiBlast({ query, blastDatabase, onProgress, onRid, signal, }) {
|
|
51
52
|
onProgress('Submitting to EBI BLAST...');
|
|
52
53
|
const jobId = await submitEbiJob({
|
|
53
54
|
tool: TOOL,
|
|
@@ -57,7 +58,8 @@ export async function queryEbiBlast({ query, blastDatabase, onProgress, onRid, }
|
|
|
57
58
|
database: blastDatabase,
|
|
58
59
|
sequence: query,
|
|
59
60
|
},
|
|
61
|
+
signal,
|
|
60
62
|
});
|
|
61
63
|
onRid(jobId);
|
|
62
|
-
return queryEbiBlastFromJobId({ jobId, onProgress });
|
|
64
|
+
return queryEbiBlastFromJobId({ jobId, onProgress, signal });
|
|
63
65
|
}
|
|
@@ -16,18 +16,21 @@ export declare const EBI_BASE = "https://www.ebi.ac.uk/Tools/services/rest";
|
|
|
16
16
|
export declare const EBI_EMAIL_STORAGE_KEY = "msa-ebiContactEmail";
|
|
17
17
|
export declare const DEFAULT_EBI_EMAIL = "colin.diesh@gmail.com";
|
|
18
18
|
export declare function getEbiEmail(): string;
|
|
19
|
-
export declare function submitEbiJob({ tool, params, }: {
|
|
19
|
+
export declare function submitEbiJob({ tool, params, signal, }: {
|
|
20
20
|
tool: string;
|
|
21
21
|
params: Record<string, string>;
|
|
22
|
+
signal?: AbortSignal;
|
|
22
23
|
}): Promise<string>;
|
|
23
|
-
export declare function waitForEbiJob({ tool, jobId, intervalSeconds, onCountdown, }: {
|
|
24
|
+
export declare function waitForEbiJob({ tool, jobId, intervalSeconds, onCountdown, signal, }: {
|
|
24
25
|
tool: string;
|
|
25
26
|
jobId: string;
|
|
26
27
|
intervalSeconds?: number;
|
|
27
28
|
onCountdown: (secondsRemaining: number) => void;
|
|
29
|
+
signal?: AbortSignal;
|
|
28
30
|
}): Promise<void>;
|
|
29
|
-
export declare function fetchEbiResult({ tool, jobId, type, }: {
|
|
31
|
+
export declare function fetchEbiResult({ tool, jobId, type, signal, }: {
|
|
30
32
|
tool: string;
|
|
31
33
|
jobId: string;
|
|
32
34
|
type: string;
|
|
35
|
+
signal?: AbortSignal;
|
|
33
36
|
}): Promise<string>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { textfetch } from './fetch';
|
|
1
|
+
import { isAbortError, textfetch } from './fetch';
|
|
2
2
|
import { pollLoop } from './poll';
|
|
3
3
|
import { readLocalStorage } from './useLocalStorage';
|
|
4
4
|
/**
|
|
@@ -24,10 +24,11 @@ export function getEbiEmail() {
|
|
|
24
24
|
}
|
|
25
25
|
/** Statuses that mean the job is over and produced no result. */
|
|
26
26
|
const FAILED_STATUSES = new Set(['ERROR', 'FAILURE', 'NOT_FOUND']);
|
|
27
|
-
export async function submitEbiJob({ tool, params, }) {
|
|
27
|
+
export async function submitEbiJob({ tool, params, signal, }) {
|
|
28
28
|
const jobId = await textfetch(`${EBI_BASE}/${tool}/run`, {
|
|
29
29
|
method: 'POST',
|
|
30
30
|
body: new URLSearchParams({ email: getEbiEmail(), ...params }),
|
|
31
|
+
signal,
|
|
31
32
|
});
|
|
32
33
|
return jobId.trim();
|
|
33
34
|
}
|
|
@@ -43,17 +44,23 @@ export async function submitEbiJob({ tool, params, }) {
|
|
|
43
44
|
* away must not be polled forever.
|
|
44
45
|
*/
|
|
45
46
|
const MAX_CONSECUTIVE_STATUS_FAILURES = 5;
|
|
46
|
-
export async function waitForEbiJob({ tool, jobId, intervalSeconds = 10, onCountdown, }) {
|
|
47
|
+
export async function waitForEbiJob({ tool, jobId, intervalSeconds = 10, onCountdown, signal, }) {
|
|
47
48
|
let consecutiveFailures = 0;
|
|
48
49
|
await pollLoop({
|
|
49
50
|
intervalSeconds,
|
|
50
51
|
onCountdown,
|
|
52
|
+
signal,
|
|
51
53
|
check: async () => {
|
|
52
54
|
let status;
|
|
53
55
|
try {
|
|
54
|
-
status = (await textfetch(`${EBI_BASE}/${tool}/status/${jobId}
|
|
56
|
+
status = (await textfetch(`${EBI_BASE}/${tool}/status/${jobId}`, { signal })).trim();
|
|
55
57
|
}
|
|
56
58
|
catch (e) {
|
|
59
|
+
// a cancelled request is the caller giving up, not EBI being
|
|
60
|
+
// unreachable, so it must end the poll rather than be retried
|
|
61
|
+
if (isAbortError(e)) {
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
57
64
|
consecutiveFailures += 1;
|
|
58
65
|
if (consecutiveFailures >= MAX_CONSECUTIVE_STATUS_FAILURES) {
|
|
59
66
|
throw new Error(`Could not reach EBI to check ${tool} job ${jobId} after ${consecutiveFailures} tries`, { cause: e });
|
|
@@ -75,6 +82,6 @@ export async function waitForEbiJob({ tool, jobId, intervalSeconds = 10, onCount
|
|
|
75
82
|
},
|
|
76
83
|
});
|
|
77
84
|
}
|
|
78
|
-
export async function fetchEbiResult({ tool, jobId, type, }) {
|
|
79
|
-
return textfetch(`${EBI_BASE}/${tool}/result/${jobId}/${type}
|
|
85
|
+
export async function fetchEbiResult({ tool, jobId, type, signal, }) {
|
|
86
|
+
return textfetch(`${EBI_BASE}/${tool}/result/${jobId}/${type}`, { signal });
|
|
80
87
|
}
|
package/dist/utils/fetch.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export declare function handleFetch(url: string, args?: RequestInit): Promise<Response>;
|
|
2
2
|
export declare function textfetch(url: string, args?: RequestInit): Promise<string>;
|
|
3
3
|
export declare function jsonfetch<T>(url: string, args?: RequestInit): Promise<T>;
|
|
4
|
-
export declare function timeout(time: number): Promise<
|
|
4
|
+
export declare function timeout(time: number, signal?: AbortSignal): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* A cancelled launch has to be told apart from a failed one: it must not render
|
|
7
|
+
* the "failed" panel, and it must not touch a model that may already be gone.
|
|
8
|
+
* `fetch` and the sleep above both reject with a DOMException named AbortError,
|
|
9
|
+
* which subclasses Error.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isAbortError(e: unknown): boolean;
|
package/dist/utils/fetch.js
CHANGED
|
@@ -36,6 +36,32 @@ export async function jsonfetch(url, args) {
|
|
|
36
36
|
const response = await handleFetch(url, args);
|
|
37
37
|
return response.json();
|
|
38
38
|
}
|
|
39
|
-
export function timeout(time) {
|
|
40
|
-
return new Promise(
|
|
39
|
+
export function timeout(time, signal) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
if (signal?.aborted) {
|
|
42
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// the listener is removed on the natural path too: one signal serves every
|
|
46
|
+
// tick of a poll, and a 10-minute job is ~600 sleeps -- leaving each
|
|
47
|
+
// listener behind accumulates them all on that one signal
|
|
48
|
+
const onAbort = () => {
|
|
49
|
+
clearTimeout(id);
|
|
50
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
51
|
+
};
|
|
52
|
+
const id = setTimeout(() => {
|
|
53
|
+
signal?.removeEventListener('abort', onAbort);
|
|
54
|
+
resolve();
|
|
55
|
+
}, time);
|
|
56
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A cancelled launch has to be told apart from a failed one: it must not render
|
|
61
|
+
* the "failed" panel, and it must not touch a model that may already be gone.
|
|
62
|
+
* `fetch` and the sleep above both reject with a DOMException named AbortError,
|
|
63
|
+
* which subclasses Error.
|
|
64
|
+
*/
|
|
65
|
+
export function isAbortError(e) {
|
|
66
|
+
return e instanceof Error && e.name === 'AbortError';
|
|
41
67
|
}
|
package/dist/utils/msa.d.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import type { MsaAlgorithm } from '../LaunchMsaView/components/BlastQuery/consts';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Build a tree from an alignment that already exists, which is what the phmmer
|
|
4
|
+
* path needs: phmmer produces the alignment itself, so there is no aligner run
|
|
5
|
+
* to take a guide tree from — and a guide tree is a byproduct of deciding
|
|
6
|
+
* progressive alignment order, not a phylogeny, so it is not what we would want
|
|
7
|
+
* even if there were one. simple_phylogeny is clustalw2's neighbour-joining on
|
|
8
|
+
* a real distance matrix, Kimura-corrected for protein distances.
|
|
9
|
+
*/
|
|
10
|
+
export declare function launchTree({ alignment, onProgress, signal, }: {
|
|
11
|
+
alignment: string;
|
|
12
|
+
onProgress: (arg: string) => void;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
export declare function launchMSA({ algorithm, sequence, onProgress, signal, }: {
|
|
3
16
|
algorithm: MsaAlgorithm;
|
|
4
17
|
sequence: string;
|
|
5
18
|
onProgress: (arg: string) => void;
|
|
19
|
+
signal?: AbortSignal;
|
|
6
20
|
}): Promise<{
|
|
7
21
|
msa: string;
|
|
8
22
|
tree: string;
|
package/dist/utils/msa.js
CHANGED
|
@@ -21,30 +21,57 @@ const algorithms = {
|
|
|
21
21
|
treeResult: 'phylotree',
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Build a tree from an alignment that already exists, which is what the phmmer
|
|
26
|
+
* path needs: phmmer produces the alignment itself, so there is no aligner run
|
|
27
|
+
* to take a guide tree from — and a guide tree is a byproduct of deciding
|
|
28
|
+
* progressive alignment order, not a phylogeny, so it is not what we would want
|
|
29
|
+
* even if there were one. simple_phylogeny is clustalw2's neighbour-joining on
|
|
30
|
+
* a real distance matrix, Kimura-corrected for protein distances.
|
|
31
|
+
*/
|
|
32
|
+
export async function launchTree({ alignment, onProgress, signal, }) {
|
|
33
|
+
const tool = 'simple_phylogeny';
|
|
34
|
+
onProgress('Building tree...');
|
|
35
|
+
const jobId = await submitEbiJob({
|
|
36
|
+
tool,
|
|
37
|
+
params: {
|
|
38
|
+
sequence: alignment,
|
|
39
|
+
tree: 'phylip',
|
|
40
|
+
clustering: 'Neighbour-joining',
|
|
41
|
+
kimura: 'true',
|
|
42
|
+
},
|
|
43
|
+
signal,
|
|
44
|
+
});
|
|
45
|
+
await waitForEbiJob({
|
|
46
|
+
tool,
|
|
47
|
+
jobId,
|
|
48
|
+
signal,
|
|
49
|
+
onCountdown: s => {
|
|
50
|
+
onProgress(`Re-checking tree status in... ${s}`);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
return fetchEbiResult({ tool, jobId, type: 'tree', signal });
|
|
54
|
+
}
|
|
55
|
+
export async function launchMSA({ algorithm, sequence, onProgress, signal, }) {
|
|
25
56
|
const config = algorithms[algorithm];
|
|
26
57
|
onProgress(`Launching ${algorithm} MSA...`);
|
|
27
58
|
const jobId = await submitEbiJob({
|
|
28
59
|
tool: algorithm,
|
|
29
60
|
params: { ...config.params, sequence },
|
|
61
|
+
signal,
|
|
30
62
|
});
|
|
31
63
|
await waitForEbiJob({
|
|
32
64
|
tool: algorithm,
|
|
33
65
|
jobId,
|
|
66
|
+
signal,
|
|
34
67
|
onCountdown: s => {
|
|
35
68
|
onProgress(`Re-checking MSA status in... ${s}`);
|
|
36
69
|
},
|
|
37
70
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
tree: await fetchEbiResult({
|
|
45
|
-
tool: algorithm,
|
|
46
|
-
jobId,
|
|
47
|
-
type: config.treeResult,
|
|
48
|
-
}),
|
|
49
|
-
};
|
|
71
|
+
// one finished job, two result files, neither derived from the other
|
|
72
|
+
const [msa, tree] = await Promise.all([
|
|
73
|
+
fetchEbiResult({ tool: algorithm, jobId, type: config.msaResult, signal }),
|
|
74
|
+
fetchEbiResult({ tool: algorithm, jobId, type: config.treeResult, signal }),
|
|
75
|
+
]);
|
|
76
|
+
return { msa, tree };
|
|
50
77
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PhmmerRow } from './phmmer';
|
|
2
|
+
import type { TaxonomyInfo } from './taxonomyNames';
|
|
3
|
+
import type { BlastHitDescription } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Turning search results into the rows the view is given, kept free of any
|
|
6
|
+
* jbrowse or network import so the whole assembly can be run and checked
|
|
7
|
+
* outside a browser — see test/phmmerLive.test.ts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildRowMetadata(desc: BlastHitDescription, taxonomyInfo: Map<number, TaxonomyInfo>): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* One target can match the query in several places and phmmer emits a row per
|
|
12
|
+
* matched envelope — four for lamprey albumin against human albumin, which has
|
|
13
|
+
* three domains. Those rows share an accession and so would share a name, and
|
|
14
|
+
* duplicate names silently collapse rows in both the MSA and the tree, so the
|
|
15
|
+
* envelope disambiguates them.
|
|
16
|
+
*/
|
|
17
|
+
export declare function makeRowNames(rows: PhmmerRow[], taxonomyInfo: Map<number, TaxonomyInfo>): string[];
|
|
18
|
+
/**
|
|
19
|
+
* The phmmer alignment as the view receives it: aligned FASTA whose first row
|
|
20
|
+
* is the query, plus the per-row metadata keyed by the same names, which are
|
|
21
|
+
* also what the tree's leaves are labelled with.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildPhmmerMsa({ rows, queryRow, taxonomyInfo, querySeqName, }: {
|
|
24
|
+
rows: PhmmerRow[];
|
|
25
|
+
queryRow: string;
|
|
26
|
+
taxonomyInfo: Map<number, TaxonomyInfo>;
|
|
27
|
+
querySeqName?: string;
|
|
28
|
+
}): {
|
|
29
|
+
msa: string;
|
|
30
|
+
treeMetadata: Record<string, Record<string, string>>;
|
|
31
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { makeId } from '../LaunchMsaView/components/util';
|
|
2
|
+
/**
|
|
3
|
+
* Turning search results into the rows the view is given, kept free of any
|
|
4
|
+
* jbrowse or network import so the whole assembly can be run and checked
|
|
5
|
+
* outside a browser — see test/phmmerLive.test.ts.
|
|
6
|
+
*/
|
|
7
|
+
export function buildRowMetadata(desc, taxonomyInfo) {
|
|
8
|
+
const metadata = {};
|
|
9
|
+
const taxInfo = desc.taxid ? taxonomyInfo.get(desc.taxid) : undefined;
|
|
10
|
+
if (taxInfo?.sciname) {
|
|
11
|
+
metadata['Scientific name'] = taxInfo.sciname;
|
|
12
|
+
}
|
|
13
|
+
if (taxInfo?.commonName) {
|
|
14
|
+
metadata['Common name'] = taxInfo.commonName;
|
|
15
|
+
}
|
|
16
|
+
if (desc.accession) {
|
|
17
|
+
metadata.Accession = desc.accession;
|
|
18
|
+
}
|
|
19
|
+
if (desc.id) {
|
|
20
|
+
metadata.ID = desc.id;
|
|
21
|
+
}
|
|
22
|
+
if (desc.title) {
|
|
23
|
+
metadata.Description = desc.title;
|
|
24
|
+
}
|
|
25
|
+
return metadata;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One target can match the query in several places and phmmer emits a row per
|
|
29
|
+
* matched envelope — four for lamprey albumin against human albumin, which has
|
|
30
|
+
* three domains. Those rows share an accession and so would share a name, and
|
|
31
|
+
* duplicate names silently collapse rows in both the MSA and the tree, so the
|
|
32
|
+
* envelope disambiguates them.
|
|
33
|
+
*/
|
|
34
|
+
export function makeRowNames(rows, taxonomyInfo) {
|
|
35
|
+
const baseNames = rows.map(row => makeId(row, taxonomyInfo));
|
|
36
|
+
const counts = new Map();
|
|
37
|
+
for (const name of baseNames) {
|
|
38
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
39
|
+
}
|
|
40
|
+
const used = new Set();
|
|
41
|
+
return baseNames.map((base, i) => {
|
|
42
|
+
let name = counts.get(base) > 1 ? `${base}_${rows[i].range ?? i + 1}` : base;
|
|
43
|
+
while (used.has(name)) {
|
|
44
|
+
name = `${name}_${i + 1}`;
|
|
45
|
+
}
|
|
46
|
+
used.add(name);
|
|
47
|
+
return name;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The phmmer alignment as the view receives it: aligned FASTA whose first row
|
|
52
|
+
* is the query, plus the per-row metadata keyed by the same names, which are
|
|
53
|
+
* also what the tree's leaves are labelled with.
|
|
54
|
+
*/
|
|
55
|
+
export function buildPhmmerMsa({ rows, queryRow, taxonomyInfo, querySeqName = 'QUERY', }) {
|
|
56
|
+
const treeMetadata = {};
|
|
57
|
+
const rowNames = makeRowNames(rows, taxonomyInfo);
|
|
58
|
+
const sequences = rows.map((row, i) => {
|
|
59
|
+
const rowName = rowNames[i];
|
|
60
|
+
treeMetadata[rowName] = buildRowMetadata(row, taxonomyInfo);
|
|
61
|
+
return `>${rowName}\n${row.aligned}`;
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
msa: [`>${querySeqName}\n${queryRow}`, ...sequences].join('\n'),
|
|
65
|
+
treeMetadata,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -5,11 +5,11 @@ export type DomainMatch = InterProScanResults['matches'][number];
|
|
|
5
5
|
* domain and site annotations, keyed by both the versioned and primary
|
|
6
6
|
* accession so callers can look up by whichever NCBI returned.
|
|
7
7
|
*/
|
|
8
|
-
export declare function parseCddDomains(xml: string): Map<string,
|
|
8
|
+
export declare function parseCddDomains(xml: string): Map<string, DomainMatch[]>;
|
|
9
9
|
/**
|
|
10
10
|
* Fetch pre-computed CDD domain and site annotations for NCBI protein
|
|
11
11
|
* accessions. These come baked into the GenPept records, so a single batched
|
|
12
12
|
* efetch returns them with no job submission or polling. Results are cached in
|
|
13
13
|
* IndexedDB so reopening a view doesn't refetch.
|
|
14
14
|
*/
|
|
15
|
-
export declare function fetchProteinDomains(accessions: string[]): Promise<Map<string,
|
|
15
|
+
export declare function fetchProteinDomains(accessions: string[]): Promise<Map<string, DomainMatch[]>>;
|
|
@@ -10,6 +10,13 @@ export interface OrthologRow {
|
|
|
10
10
|
protein: string;
|
|
11
11
|
sequence: string;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* A candidate gene reference as the ortholog services know it: a GFF ID prefix
|
|
15
|
+
* (gene:TP53) and a version suffix (NM_000546.6) stripped off. Shared with
|
|
16
|
+
* pantherOrthologs, which asks PANTHER the same question with the same
|
|
17
|
+
* candidates.
|
|
18
|
+
*/
|
|
19
|
+
export declare function cleanGeneCandidate(raw: string): string;
|
|
13
20
|
/**
|
|
14
21
|
* A free-text gene reference -> NCBI gene id. A bare number is taken as the id
|
|
15
22
|
* itself; anything else is searched as a gene name within the query taxon.
|
|
@@ -50,7 +57,10 @@ export declare function parseFasta(text: string): Map<string, string>;
|
|
|
50
57
|
* Sanitized, unique single-token labels used identically in the FASTA headers,
|
|
51
58
|
* the tree leaf names and the domain GFF seq_ids — that identity is how the
|
|
52
59
|
* viewer pairs a tree leaf to its alignment row to its domain track. Collisions
|
|
53
|
-
* get a numeric suffix rather than silently overwriting a row
|
|
60
|
+
* get a numeric suffix rather than silently overwriting a row, and the suffix
|
|
61
|
+
* climbs past labels already emitted: a species genuinely named `human_2` would
|
|
62
|
+
* otherwise take the label the second `human` is about to get, merging two rows
|
|
63
|
+
* into one.
|
|
54
64
|
*/
|
|
55
65
|
export declare function dedupeLabels(names: string[]): string[];
|
|
56
66
|
/**
|