jbrowse-plugin-msaview 2.6.6 → 2.6.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.
Files changed (34) hide show
  1. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js +1 -1
  2. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +2 -2
  3. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +1 -1
  4. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +1 -2
  5. package/dist/LaunchMsaView/components/calculateProteinSequence.js +4 -2
  6. package/dist/MsaViewPanel/msaDataStore.js +7 -2
  7. package/dist/MsaViewPanel/util.d.ts +0 -11
  8. package/dist/MsaViewPanel/util.js +0 -7
  9. package/dist/jbrowse-plugin-msaview.umd.production.min.js +24 -24
  10. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +3 -3
  11. package/dist/utils/blastCache.js +13 -5
  12. package/dist/utils/domainCache.js +7 -2
  13. package/dist/utils/fetch.d.ts +0 -2
  14. package/dist/utils/fetch.js +0 -21
  15. package/dist/utils/ncbiDomains.js +10 -3
  16. package/dist/utils/taxonomyNames.js +7 -2
  17. package/dist/version.d.ts +1 -1
  18. package/dist/version.js +1 -1
  19. package/package.json +1 -2
  20. package/src/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.ts +1 -1
  21. package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +2 -1
  22. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +1 -1
  23. package/src/LaunchMsaView/components/calculateProteinSequence.ts +4 -3
  24. package/src/MsaViewPanel/msaDataStore.ts +8 -2
  25. package/src/MsaViewPanel/util.ts +0 -24
  26. package/src/utils/blastCache.ts +14 -3
  27. package/src/utils/domainCache.ts +8 -2
  28. package/src/utils/fetch.ts +0 -28
  29. package/src/utils/ncbiDomains.ts +10 -3
  30. package/src/utils/taxonomyNames.ts +8 -2
  31. package/src/version.ts +1 -1
  32. package/dist/LaunchMsaView/components/ManualMSALoader/fetchGeneList.d.ts +0 -1
  33. package/dist/LaunchMsaView/components/ManualMSALoader/fetchGeneList.js +0 -11
  34. package/src/LaunchMsaView/components/ManualMSALoader/fetchGeneList.ts +0 -13
@@ -2,8 +2,9 @@ import { openDB } 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
- async function getDB() {
6
- return openDB(DB_NAME, DB_VERSION, {
5
+ let dbPromise;
6
+ function getDB() {
7
+ dbPromise ??= openDB(DB_NAME, DB_VERSION, {
7
8
  upgrade(db, oldVersion) {
8
9
  if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
9
10
  db.deleteObjectStore(STORE_NAME);
@@ -12,15 +13,22 @@ async function getDB() {
12
13
  db.createObjectStore(STORE_NAME, { keyPath: 'id' });
13
14
  }
14
15
  },
16
+ }).catch((e) => {
17
+ dbPromise = undefined;
18
+ throw e;
15
19
  });
20
+ return dbPromise;
16
21
  }
17
- function createCacheKey(proteinSequence, blastDatabase, blastProgram, transcriptId) {
22
+ function createCacheKey(proteinSequence, blastDatabase, blastProgram, msaAlgorithm, transcriptId) {
18
23
  const idPart = transcriptId ? `:${transcriptId}` : '';
19
- return `${blastDatabase}:${blastProgram}${idPart}:${proteinSequence}`;
24
+ // msaAlgorithm is part of the key because the stored msa/tree are produced by
25
+ // it — without it, re-running the same query under a different algorithm
26
+ // overwrites the earlier result and drops it from the history list
27
+ return `${blastDatabase}:${blastProgram}:${msaAlgorithm}${idPart}:${proteinSequence}`;
20
28
  }
21
29
  export async function saveBlastResult({ proteinSequence, blastDatabase, blastProgram, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }) {
22
30
  const db = await getDB();
23
- const id = createCacheKey(proteinSequence, blastDatabase, blastProgram, transcriptId);
31
+ const id = createCacheKey(proteinSequence, blastDatabase, blastProgram, msaAlgorithm, transcriptId);
24
32
  const entry = {
25
33
  id,
26
34
  proteinSequence,
@@ -2,14 +2,19 @@ import { openDB } from 'idb';
2
2
  const DB_NAME = 'jbrowse-msaview-domain-cache';
3
3
  const STORE_NAME = 'domains';
4
4
  const DB_VERSION = 1;
5
- async function getDB() {
6
- return openDB(DB_NAME, DB_VERSION, {
5
+ let dbPromise;
6
+ function getDB() {
7
+ dbPromise ??= openDB(DB_NAME, DB_VERSION, {
7
8
  upgrade(db) {
8
9
  if (!db.objectStoreNames.contains(STORE_NAME)) {
9
10
  db.createObjectStore(STORE_NAME, { keyPath: 'accession' });
10
11
  }
11
12
  },
13
+ }).catch((e) => {
14
+ dbPromise = undefined;
15
+ throw e;
12
16
  });
17
+ return dbPromise;
13
18
  }
14
19
  export async function getCachedDomains(accessions) {
15
20
  const db = await getDB();
@@ -2,5 +2,3 @@ export declare function handleFetch(url: string, args?: RequestInit): Promise<Re
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
4
  export declare function timeout(time: number): Promise<unknown>;
5
- export declare function fetchWithLocalStorageCache<T>(key: string, fetchFn: () => Promise<T>): Promise<T>;
6
- export declare function unzipfetch(url: string, arg?: RequestInit): Promise<any>;
@@ -1,4 +1,3 @@
1
- import { ungzip } from 'pako-esm2';
2
1
  export async function handleFetch(url, args) {
3
2
  const response = await fetch(url, args);
4
3
  if (!response.ok) {
@@ -17,23 +16,3 @@ export async function jsonfetch(url, args) {
17
16
  export function timeout(time) {
18
17
  return new Promise(res => setTimeout(res, time));
19
18
  }
20
- export async function fetchWithLocalStorageCache(key, fetchFn) {
21
- const cachedData = localStorage.getItem(key);
22
- if (cachedData) {
23
- try {
24
- return JSON.parse(cachedData);
25
- }
26
- catch (error) {
27
- console.error(`Error parsing cached data for ${key}:`, error);
28
- // Continue to fetch fresh data if parsing fails
29
- localStorage.removeItem(key);
30
- }
31
- }
32
- const data = await fetchFn();
33
- localStorage.setItem(key, JSON.stringify(data));
34
- return data;
35
- }
36
- export async function unzipfetch(url, arg) {
37
- const res = await handleFetch(url, arg);
38
- return ungzip(await res.arrayBuffer(), { to: 'string' });
39
- }
@@ -144,9 +144,16 @@ export async function fetchProteinDomains(accessions) {
144
144
  }));
145
145
  const parsed = parseCddDomains(xml);
146
146
  for (const acc of batch) {
147
- const matches = parsed.get(acc) ?? [];
148
- byAccession.set(acc, matches);
149
- toCache.push({ accession: acc, matches });
147
+ // only cache accessions actually present in the response: a genuinely
148
+ // domain-less protein comes back as an empty array (cache it), but an
149
+ // accession the batch omitted comes back undefined — caching [] for it
150
+ // would permanently hide its domains on later loads (the domain cache
151
+ // never expires), so leave it uncached to be retried
152
+ const matches = parsed.get(acc);
153
+ if (matches !== undefined) {
154
+ byAccession.set(acc, matches);
155
+ toCache.push({ accession: acc, matches });
156
+ }
150
157
  }
151
158
  }
152
159
  if (toCache.length > 0) {
@@ -3,15 +3,20 @@ import { efetchUrl } from './eutils';
3
3
  const DB_NAME = 'jbrowse-msaview-taxonomy-cache';
4
4
  const STORE_NAME = 'common-names';
5
5
  const DB_VERSION = 2;
6
- async function getDB() {
7
- return openDB(DB_NAME, DB_VERSION, {
6
+ let dbPromise;
7
+ function getDB() {
8
+ dbPromise ??= openDB(DB_NAME, DB_VERSION, {
8
9
  upgrade(db) {
9
10
  if (db.objectStoreNames.contains(STORE_NAME)) {
10
11
  db.deleteObjectStore(STORE_NAME);
11
12
  }
12
13
  db.createObjectStore(STORE_NAME, { keyPath: 'taxid' });
13
14
  },
15
+ }).catch((e) => {
16
+ dbPromise = undefined;
17
+ throw e;
14
18
  });
19
+ return dbPromise;
15
20
  }
16
21
  async function getCachedTaxonomies(taxids) {
17
22
  const db = await getDB();
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.6.6";
1
+ export declare const version = "2.6.7";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.6.6';
1
+ export const version = '2.6.7';
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.6.6",
2
+ "version": "2.6.7",
3
3
  "license": "MIT",
4
4
  "name": "jbrowse-plugin-msaview",
5
5
  "repository": {
@@ -20,7 +20,6 @@
20
20
  "@gmod/bgzf-filehandle": "^6.2.0",
21
21
  "g2p_mapper": "^2.1.5",
22
22
  "idb": "^8.0.3",
23
- "pako-esm2": "^2.0.2",
24
23
  "react-msaview": "^5.5.0",
25
24
  "swr": "^2.4.2"
26
25
  },
@@ -56,7 +56,7 @@ export default class BgzipFastaMsaAdapter extends BaseAdapter {
56
56
 
57
57
  async getMSA(id: string) {
58
58
  const adapter = await this.configure()
59
- const refNames = await adapter.getRefNames()
59
+ const refNames = await this.getMSARefs()
60
60
  const rows = refNames.filter(refName => this.refNameToMsaId(refName) === id)
61
61
  return firstValueFrom(
62
62
  adapter
@@ -11,6 +11,7 @@ export function useCachedBlastResults(geneIds: string[]) {
11
11
  const {
12
12
  data: results,
13
13
  error,
14
+ isLoading,
14
15
  mutate,
15
16
  } = useSWR(
16
17
  `cached-blast-${geneIds.join(',')}`,
@@ -37,7 +38,7 @@ export function useCachedBlastResults(geneIds: string[]) {
37
38
  return {
38
39
  results: results ?? [],
39
40
  error,
40
- isLoading: !results && !error,
41
+ isLoading,
41
42
  handleDelete,
42
43
  handleClearAll,
43
44
  }
@@ -70,7 +70,7 @@ const PreLoadedMSA = observer(function ({
70
70
  error: msaDataFetchError,
71
71
  } = useSWR(
72
72
  selectedId && selectedDataset && msaList
73
- ? `${selectedDataset.datasetId}-${selectedId}-${msaList.length}-msa`
73
+ ? `${selectedDataset.datasetId}-${selectedId}-msa`
74
74
  : null,
75
75
  () =>
76
76
  fetchMSA({
@@ -8,6 +8,10 @@ import {
8
8
  import type { Feat } from './types'
9
9
  import type { Feature } from '@jbrowse/core/util'
10
10
 
11
+ // pure constant: the standard codon table never varies, so build it once rather
12
+ // than on every translation (which runs on every panel re-render)
13
+ const codonTable = generateCodonTable(defaultCodonTable)
14
+
11
15
  export function stitch(subfeats: Feat[], sequence: string) {
12
16
  return subfeats.map(sub => sequence.slice(sub.start, sub.end)).join('')
13
17
  }
@@ -15,11 +19,9 @@ export function stitch(subfeats: Feat[], sequence: string) {
15
19
  export function calculateProteinSequence({
16
20
  cds,
17
21
  sequence,
18
- codonTable,
19
22
  }: {
20
23
  cds: Feat[]
21
24
  sequence: string
22
- codonTable: Record<string, string>
23
25
  }) {
24
26
  const str = stitch(cds, sequence)
25
27
  let protein = ''
@@ -63,6 +65,5 @@ export function getProteinSequenceFromFeature({
63
65
  return calculateProteinSequence({
64
66
  cds: strand === -1 ? revlist(cds, seq.length) : cds,
65
67
  sequence: strand === -1 ? revcom(seq) : seq,
66
- codonTable: generateCodonTable(defaultCodonTable),
67
68
  })
68
69
  }
@@ -12,15 +12,21 @@ interface StoredMsaData {
12
12
  timestamp: number
13
13
  }
14
14
 
15
- async function getDB() {
16
- return openDB(DB_NAME, DB_VERSION, {
15
+ let dbPromise: ReturnType<typeof openDB> | undefined
16
+
17
+ function getDB() {
18
+ dbPromise ??= openDB(DB_NAME, DB_VERSION, {
17
19
  upgrade(db) {
18
20
  if (!db.objectStoreNames.contains(STORE_NAME)) {
19
21
  const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' })
20
22
  store.createIndex('timestamp', 'timestamp', { unique: false })
21
23
  }
22
24
  },
25
+ }).catch((e: unknown) => {
26
+ dbPromise = undefined
27
+ throw e
23
28
  })
29
+ return dbPromise
24
30
  }
25
31
 
26
32
  export function generateDataStoreId() {
@@ -9,30 +9,6 @@ export function hasHoverPosition(
9
9
  )
10
10
  }
11
11
 
12
- interface AssemblyManagerLike {
13
- get: (
14
- name: string,
15
- ) => { getCanonicalRefName: (r: string) => string | undefined } | undefined
16
- }
17
-
18
- export function getCanonicalRefName({
19
- assemblyManager,
20
- assemblyNames,
21
- refName,
22
- }: {
23
- assemblyManager: AssemblyManagerLike
24
- assemblyNames: string[] | undefined
25
- refName: string
26
- }) {
27
- const assemblyName = assemblyNames?.[0]
28
- if (assemblyName) {
29
- return (
30
- assemblyManager.get(assemblyName)?.getCanonicalRefName(refName) ?? refName
31
- )
32
- }
33
- return refName
34
- }
35
-
36
12
  /**
37
13
  * Extracts UniProt ID from an AlphaFold URL
38
14
  * Examples:
@@ -27,8 +27,10 @@ export interface CachedBlastResult {
27
27
  geneName?: string
28
28
  }
29
29
 
30
- async function getDB() {
31
- return openDB(DB_NAME, DB_VERSION, {
30
+ let dbPromise: ReturnType<typeof openDB> | undefined
31
+
32
+ function getDB() {
33
+ dbPromise ??= openDB(DB_NAME, DB_VERSION, {
32
34
  upgrade(db, oldVersion) {
33
35
  if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
34
36
  db.deleteObjectStore(STORE_NAME)
@@ -37,17 +39,25 @@ async function getDB() {
37
39
  db.createObjectStore(STORE_NAME, { keyPath: 'id' })
38
40
  }
39
41
  },
42
+ }).catch((e: unknown) => {
43
+ dbPromise = undefined
44
+ throw e
40
45
  })
46
+ return dbPromise
41
47
  }
42
48
 
43
49
  function createCacheKey(
44
50
  proteinSequence: string,
45
51
  blastDatabase: BlastDatabase,
46
52
  blastProgram: BlastProgram,
53
+ msaAlgorithm: MsaAlgorithm,
47
54
  transcriptId?: string,
48
55
  ) {
49
56
  const idPart = transcriptId ? `:${transcriptId}` : ''
50
- return `${blastDatabase}:${blastProgram}${idPart}:${proteinSequence}`
57
+ // msaAlgorithm is part of the key because the stored msa/tree are produced by
58
+ // it — without it, re-running the same query under a different algorithm
59
+ // overwrites the earlier result and drops it from the history list
60
+ return `${blastDatabase}:${blastProgram}:${msaAlgorithm}${idPart}:${proteinSequence}`
51
61
  }
52
62
 
53
63
  export async function saveBlastResult({
@@ -82,6 +92,7 @@ export async function saveBlastResult({
82
92
  proteinSequence,
83
93
  blastDatabase,
84
94
  blastProgram,
95
+ msaAlgorithm,
85
96
  transcriptId,
86
97
  )
87
98
  const entry: CachedBlastResult = {
@@ -11,14 +11,20 @@ interface CachedDomain {
11
11
  matches: DomainMatch[]
12
12
  }
13
13
 
14
- async function getDB() {
15
- return openDB(DB_NAME, DB_VERSION, {
14
+ let dbPromise: ReturnType<typeof openDB> | undefined
15
+
16
+ function getDB() {
17
+ dbPromise ??= openDB(DB_NAME, DB_VERSION, {
16
18
  upgrade(db) {
17
19
  if (!db.objectStoreNames.contains(STORE_NAME)) {
18
20
  db.createObjectStore(STORE_NAME, { keyPath: 'accession' })
19
21
  }
20
22
  },
23
+ }).catch((e: unknown) => {
24
+ dbPromise = undefined
25
+ throw e
21
26
  })
27
+ return dbPromise
22
28
  }
23
29
 
24
30
  export async function getCachedDomains(accessions: string[]) {
@@ -1,5 +1,3 @@
1
- import { ungzip } from 'pako-esm2'
2
-
3
1
  export async function handleFetch(url: string, args?: RequestInit) {
4
2
  const response = await fetch(url, args)
5
3
 
@@ -25,29 +23,3 @@ export async function jsonfetch<T>(url: string, args?: RequestInit) {
25
23
  export function timeout(time: number) {
26
24
  return new Promise(res => setTimeout(res, time))
27
25
  }
28
-
29
- export async function fetchWithLocalStorageCache<T>(
30
- key: string,
31
- fetchFn: () => Promise<T>,
32
- ): Promise<T> {
33
- const cachedData = localStorage.getItem(key)
34
-
35
- if (cachedData) {
36
- try {
37
- return JSON.parse(cachedData) as T
38
- } catch (error) {
39
- console.error(`Error parsing cached data for ${key}:`, error)
40
- // Continue to fetch fresh data if parsing fails
41
- localStorage.removeItem(key)
42
- }
43
- }
44
-
45
- const data = await fetchFn()
46
- localStorage.setItem(key, JSON.stringify(data))
47
- return data
48
- }
49
-
50
- export async function unzipfetch(url: string, arg?: RequestInit) {
51
- const res = await handleFetch(url, arg)
52
- return ungzip(await res.arrayBuffer(), { to: 'string' })
53
- }
@@ -161,9 +161,16 @@ export async function fetchProteinDomains(accessions: string[]) {
161
161
  )
162
162
  const parsed = parseCddDomains(xml)
163
163
  for (const acc of batch) {
164
- const matches = parsed.get(acc) ?? []
165
- byAccession.set(acc, matches)
166
- toCache.push({ accession: acc, matches })
164
+ // only cache accessions actually present in the response: a genuinely
165
+ // domain-less protein comes back as an empty array (cache it), but an
166
+ // accession the batch omitted comes back undefined — caching [] for it
167
+ // would permanently hide its domains on later loads (the domain cache
168
+ // never expires), so leave it uncached to be retried
169
+ const matches = parsed.get(acc)
170
+ if (matches !== undefined) {
171
+ byAccession.set(acc, matches)
172
+ toCache.push({ accession: acc, matches })
173
+ }
167
174
  }
168
175
  }
169
176
  if (toCache.length > 0) {
@@ -12,15 +12,21 @@ interface CachedTaxonomy {
12
12
  commonName?: string
13
13
  }
14
14
 
15
- async function getDB() {
16
- return openDB(DB_NAME, DB_VERSION, {
15
+ let dbPromise: ReturnType<typeof openDB> | undefined
16
+
17
+ function getDB() {
18
+ dbPromise ??= openDB(DB_NAME, DB_VERSION, {
17
19
  upgrade(db) {
18
20
  if (db.objectStoreNames.contains(STORE_NAME)) {
19
21
  db.deleteObjectStore(STORE_NAME)
20
22
  }
21
23
  db.createObjectStore(STORE_NAME, { keyPath: 'taxid' })
22
24
  },
25
+ }).catch((e: unknown) => {
26
+ dbPromise = undefined
27
+ throw e
23
28
  })
29
+ return dbPromise
24
30
  }
25
31
 
26
32
  async function getCachedTaxonomies(taxids: number[]) {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '2.6.6'
1
+ export const version = '2.6.7'
@@ -1 +0,0 @@
1
- export declare function fetchGeneList(): Promise<string[]>;
@@ -1,11 +0,0 @@
1
- export async function fetchGeneList() {
2
- const res = await fetch('https://jbrowse.org/demos/msaview/knownCanonical/list.txt');
3
- if (!res.ok) {
4
- throw new Error(`HTTP ${res.status} fetching list ${await res.text()}`);
5
- }
6
- const result = await res.text();
7
- return result
8
- .split('\n')
9
- .map(f => f.trim())
10
- .filter(f => !!f);
11
- }
@@ -1,13 +0,0 @@
1
- export async function fetchGeneList() {
2
- const res = await fetch(
3
- 'https://jbrowse.org/demos/msaview/knownCanonical/list.txt',
4
- )
5
- if (!res.ok) {
6
- throw new Error(`HTTP ${res.status} fetching list ${await res.text()}`)
7
- }
8
- const result = await res.text()
9
- return result
10
- .split('\n')
11
- .map(f => f.trim())
12
- .filter(f => !!f)
13
- }