jbrowse-plugin-msaview 2.7.1 → 2.7.3

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 (59) hide show
  1. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +6 -5
  2. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +1 -1
  3. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js +1 -1
  4. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.d.ts +1 -1
  5. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +5 -2
  6. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +3 -2
  7. package/dist/LaunchMsaView/components/calculateProteinSequence.js +31 -15
  8. package/dist/LaunchMsaView/components/geneticCodes.d.ts +15 -0
  9. package/dist/LaunchMsaView/components/geneticCodes.js +227 -0
  10. package/dist/LaunchMsaView/components/types.d.ts +1 -0
  11. package/dist/LaunchMsaView/index.js +32 -26
  12. package/dist/MsaViewPanel/doLaunchBlast.js +10 -22
  13. package/dist/MsaViewPanel/genomeToMSA.js +13 -6
  14. package/dist/MsaViewPanel/genomeToMSA.test.js +35 -6
  15. package/dist/MsaViewPanel/model.d.ts +68 -66
  16. package/dist/MsaViewPanel/model.js +14 -12
  17. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +32 -13
  18. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +43 -14
  19. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +108 -18
  20. package/dist/MsaViewPanel/msaDataStore.js +8 -17
  21. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +8 -6
  22. package/dist/jbrowse-plugin-msaview.umd.production.min.js +31 -31
  23. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  24. package/dist/utils/blastCache.d.ts +1 -2
  25. package/dist/utils/blastCache.js +9 -22
  26. package/dist/utils/domainCache.js +6 -15
  27. package/dist/utils/idb.d.ts +12 -0
  28. package/dist/utils/idb.js +21 -0
  29. package/dist/utils/taxonomyNames.js +13 -18
  30. package/dist/utils/useLocalStorage.d.ts +1 -0
  31. package/dist/utils/useLocalStorage.js +29 -0
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +5 -3
  35. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +6 -8
  36. package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +1 -1
  37. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.tsx +1 -1
  38. package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +4 -2
  39. package/src/LaunchMsaView/components/calculateProteinSequence.ts +35 -20
  40. package/src/LaunchMsaView/components/geneticCodes.ts +298 -0
  41. package/src/LaunchMsaView/components/types.ts +3 -0
  42. package/src/LaunchMsaView/index.ts +44 -35
  43. package/src/MsaViewPanel/doLaunchBlast.ts +20 -29
  44. package/src/MsaViewPanel/genomeToMSA.test.ts +38 -6
  45. package/src/MsaViewPanel/genomeToMSA.ts +14 -6
  46. package/src/MsaViewPanel/model.ts +17 -12
  47. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +117 -18
  48. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +70 -26
  49. package/src/MsaViewPanel/msaDataStore.ts +17 -17
  50. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +8 -6
  51. package/src/utils/blastCache.ts +20 -23
  52. package/src/utils/domainCache.ts +14 -18
  53. package/src/utils/idb.ts +28 -0
  54. package/src/utils/taxonomyNames.ts +22 -24
  55. package/src/utils/useLocalStorage.ts +31 -0
  56. package/src/version.ts +1 -1
  57. package/dist/MsaViewPanel/blosum62.d.ts +0 -2
  58. package/dist/MsaViewPanel/blosum62.js +0 -627
  59. package/src/MsaViewPanel/blosum62.ts +0 -628
@@ -29,6 +29,5 @@ export declare function saveBlastResult({ proteinSequence, blastDatabase, blastP
29
29
  transcriptName?: string;
30
30
  geneName?: string;
31
31
  }): Promise<CachedBlastResult>;
32
- export declare function getAllCachedResults(): Promise<any[]>;
32
+ export declare function getAllCachedResults(): Promise<CachedBlastResult[]>;
33
33
  export declare function deleteCachedResult(id: string): Promise<void>;
34
- export declare function clearAllCachedResults(): Promise<void>;
@@ -1,24 +1,15 @@
1
- import { openDB } from 'idb';
1
+ 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
- let dbPromise;
6
- function getDB() {
7
- dbPromise ??= openDB(DB_NAME, DB_VERSION, {
8
- upgrade(db, oldVersion) {
9
- if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
10
- db.deleteObjectStore(STORE_NAME);
11
- }
12
- if (!db.objectStoreNames.contains(STORE_NAME)) {
13
- db.createObjectStore(STORE_NAME, { keyPath: 'id' });
14
- }
15
- },
16
- }).catch((e) => {
17
- dbPromise = undefined;
18
- throw e;
19
- });
20
- return dbPromise;
21
- }
5
+ const getDB = createDbOpener(DB_NAME, DB_VERSION, (db, oldVersion) => {
6
+ if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
7
+ db.deleteObjectStore(STORE_NAME);
8
+ }
9
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
10
+ db.createObjectStore(STORE_NAME, { keyPath: 'id' });
11
+ }
12
+ });
22
13
  function createCacheKey(proteinSequence, blastDatabase, blastProgram, msaAlgorithm, transcriptId) {
23
14
  const idPart = transcriptId ? `:${transcriptId}` : '';
24
15
  // msaAlgorithm is part of the key because the stored msa/tree are produced by
@@ -57,7 +48,3 @@ export async function deleteCachedResult(id) {
57
48
  const db = await getDB();
58
49
  await db.delete(STORE_NAME, id);
59
50
  }
60
- export async function clearAllCachedResults() {
61
- const db = await getDB();
62
- await db.clear(STORE_NAME);
63
- }
@@ -1,21 +1,12 @@
1
- import { openDB } from 'idb';
1
+ import { createDbOpener } from './idb';
2
2
  const DB_NAME = 'jbrowse-msaview-domain-cache';
3
3
  const STORE_NAME = 'domains';
4
4
  const DB_VERSION = 1;
5
- let dbPromise;
6
- function getDB() {
7
- dbPromise ??= openDB(DB_NAME, DB_VERSION, {
8
- upgrade(db) {
9
- if (!db.objectStoreNames.contains(STORE_NAME)) {
10
- db.createObjectStore(STORE_NAME, { keyPath: 'accession' });
11
- }
12
- },
13
- }).catch((e) => {
14
- dbPromise = undefined;
15
- throw e;
16
- });
17
- return dbPromise;
18
- }
5
+ const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
6
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
7
+ db.createObjectStore(STORE_NAME, { keyPath: 'accession' });
8
+ }
9
+ });
19
10
  export async function getCachedDomains(accessions) {
20
11
  const db = await getDB();
21
12
  const tx = db.transaction(STORE_NAME, 'readonly');
@@ -0,0 +1,12 @@
1
+ import type { DBSchema, IDBPDatabase, OpenDBCallbacks } from 'idb';
2
+ /**
3
+ * Memoized `openDB` for a typed schema, shared by this plugin's caches.
4
+ *
5
+ * The connection promise is cached so callers share one connection, and dropped
6
+ * again if the open fails, so a later call retries instead of replaying the same
7
+ * rejection forever (IndexedDB is unavailable in some private-browsing modes).
8
+ *
9
+ * Passing a DBSchema is what keeps `get`/`getAll` from returning `any`: with an
10
+ * untyped database every cached record reaches the UI unchecked.
11
+ */
12
+ export declare function createDbOpener<T extends DBSchema>(name: string, version: number, upgrade: OpenDBCallbacks<T>['upgrade']): () => Promise<IDBPDatabase<T>>;
@@ -0,0 +1,21 @@
1
+ import { openDB } from 'idb';
2
+ /**
3
+ * Memoized `openDB` for a typed schema, shared by this plugin's caches.
4
+ *
5
+ * The connection promise is cached so callers share one connection, and dropped
6
+ * again if the open fails, so a later call retries instead of replaying the same
7
+ * rejection forever (IndexedDB is unavailable in some private-browsing modes).
8
+ *
9
+ * Passing a DBSchema is what keeps `get`/`getAll` from returning `any`: with an
10
+ * untyped database every cached record reaches the UI unchecked.
11
+ */
12
+ export function createDbOpener(name, version, upgrade) {
13
+ let dbPromise;
14
+ return () => {
15
+ dbPromise ??= openDB(name, version, { upgrade }).catch((e) => {
16
+ dbPromise = undefined;
17
+ throw e;
18
+ });
19
+ return dbPromise;
20
+ };
21
+ }
@@ -1,23 +1,15 @@
1
- import { openDB } from 'idb';
2
1
  import { efetchUrl } from './eutils';
2
+ import { textfetch } from './fetch';
3
+ import { createDbOpener } from './idb';
3
4
  const DB_NAME = 'jbrowse-msaview-taxonomy-cache';
4
5
  const STORE_NAME = 'common-names';
5
6
  const DB_VERSION = 2;
6
- let dbPromise;
7
- function getDB() {
8
- dbPromise ??= openDB(DB_NAME, DB_VERSION, {
9
- upgrade(db) {
10
- if (db.objectStoreNames.contains(STORE_NAME)) {
11
- db.deleteObjectStore(STORE_NAME);
12
- }
13
- db.createObjectStore(STORE_NAME, { keyPath: 'taxid' });
14
- },
15
- }).catch((e) => {
16
- dbPromise = undefined;
17
- throw e;
18
- });
19
- return dbPromise;
20
- }
7
+ const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
8
+ if (db.objectStoreNames.contains(STORE_NAME)) {
9
+ db.deleteObjectStore(STORE_NAME);
10
+ }
11
+ db.createObjectStore(STORE_NAME, { keyPath: 'taxid' });
12
+ });
21
13
  async function getCachedTaxonomies(taxids) {
22
14
  const db = await getDB();
23
15
  const tx = db.transaction(STORE_NAME, 'readonly');
@@ -59,8 +51,11 @@ export async function fetchTaxonomyInfo(taxids) {
59
51
  const batch = uncachedTaxids.slice(i, i + batchSize);
60
52
  const idsParam = batch.join(',');
61
53
  try {
62
- const response = await fetch(efetchUrl({ db: 'taxonomy', id: idsParam, retmode: 'xml' }));
63
- const text = await response.text();
54
+ // textfetch rather than a bare fetch: an NCBI 429/5xx returns an HTML
55
+ // error body that the regexes below silently find nothing in, so without
56
+ // the status check a throttled batch looks like "these taxa have no
57
+ // names" instead of reporting why
58
+ const text = await textfetch(efetchUrl({ db: 'taxonomy', id: idsParam, retmode: 'xml' }));
64
59
  // Build a map of taxid -> taxon block by finding Taxon elements.
65
60
  // Prefer entries with <LineageEx> (full top-level entries) over nested
66
61
  // entries inside another taxon's LineageEx
@@ -0,0 +1 @@
1
+ export declare function useLocalStorage<T>(key: string, initialValue: T): readonly [T, (value: T) => void];
@@ -0,0 +1,29 @@
1
+ import { useState } from 'react';
2
+ // Vendored rather than imported from `@jbrowse/core/util`: that barrel is
3
+ // host-provided, and a barrel split dropped this export, making the BLAST panel
4
+ // throw "(0, PR.useLocalStorage) is not a function" on hosts built during that
5
+ // window. Same failure mode as `defaultCodonTable`; keeping our own copy takes
6
+ // this plugin out of the whack-a-mole.
7
+ function readLocalStorage(key, initialValue) {
8
+ try {
9
+ const item = globalThis.localStorage.getItem(key);
10
+ return item === null ? initialValue : JSON.parse(item);
11
+ }
12
+ catch (error) {
13
+ console.error(error);
14
+ return initialValue;
15
+ }
16
+ }
17
+ export function useLocalStorage(key, initialValue) {
18
+ const [storedValue, setStoredValue] = useState(() => readLocalStorage(key, initialValue));
19
+ const setValue = (value) => {
20
+ setStoredValue(value);
21
+ try {
22
+ globalThis.localStorage.setItem(key, JSON.stringify(value));
23
+ }
24
+ catch (error) {
25
+ console.error(error);
26
+ }
27
+ };
28
+ return [storedValue, setValue];
29
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.7.1";
1
+ export declare const version = "2.7.3";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.7.1';
1
+ export const version = '2.7.3';
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.7.1",
2
+ "version": "2.7.3",
3
3
  "license": "MIT",
4
4
  "name": "jbrowse-plugin-msaview",
5
5
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "puppeteer": "^25.3.0",
52
52
  "react": "^19.2.8",
53
53
  "react-dom": "^19.2.8",
54
- "react-msaview": "^5.6.3",
54
+ "react-msaview": "^5.7.0",
55
55
  "rimraf": "^6.1.3",
56
56
  "rxjs": "^7.8.2",
57
57
  "serve": "^14.2.6",
@@ -74,7 +74,9 @@
74
74
  "test:setup:version": "node scripts/test-versions.mjs setup",
75
75
  "test:versions": "node scripts/test-versions.mjs run",
76
76
  "test:version": "node scripts/test-versions.mjs run",
77
- "preversion": "pnpm lint",
77
+ "host-compat": "node scripts/host-compat-probe.mjs --bundle dist/jbrowse-plugin-msaview.umd.production.min.js",
78
+ "check-ci": "node scripts/require-green-ci.mjs",
79
+ "preversion": "pnpm check-ci && pnpm lint && pnpm build && pnpm host-compat",
78
80
  "version": "node -e \"console.log('export const version = \\'' + require('./package.json').version + '\\'')\" > src/version.ts && git add src/version.ts",
79
81
  "postversion": "git push --follow-tags"
80
82
  }
@@ -23,14 +23,12 @@ const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({
23
23
  // The persistent click selection always shows. The hover codon is suppressed
24
24
  // while hovering the LGV — GenomeMouseoverHighlight handles the single-bp
25
25
  // display in that case, so we don't stack a wider codon band on top of it.
26
- const clickHighlight = msaView?.connectedClickHighlight
27
- const hoverHighlight = hasHoverPosition(hovered)
28
- ? undefined
29
- : msaView?.connectedHoverHighlight
30
- const highlights = [clickHighlight, hoverHighlight].filter(
31
- (r): r is { refName: string; start: number; end: number } =>
32
- r !== undefined,
33
- )
26
+ const highlights = [
27
+ ...(msaView?.connectedClickHighlights ?? []),
28
+ ...(hasHoverPosition(hovered)
29
+ ? []
30
+ : (msaView?.connectedHoverHighlights ?? [])),
31
+ ]
34
32
 
35
33
  return highlights.length ? (
36
34
  <MsaToGenomeHighlightRenderer model={model} highlights={highlights} />
@@ -117,7 +117,7 @@ const CachedBlastResults = observer(function ({
117
117
  }
118
118
  }}
119
119
  >
120
- Clear All
120
+ Clear results for this gene
121
121
  </Button>
122
122
  </div>
123
123
  <List dense className={classes.resultList}>
@@ -1,6 +1,5 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
- import { useLocalStorage } from '@jbrowse/core/util'
4
3
  import SettingsIcon from '@mui/icons-material/Settings'
5
4
  import { IconButton } from '@mui/material'
6
5
  import { makeStyles } from 'tss-react/mui'
@@ -11,6 +10,7 @@ import NCBIBlastMethodSelector from './NCBIBlastMethodSelector'
11
10
  import NCBIBlastRIDPanel from './NCBIBlastRIDPanel'
12
11
  import NCBISettingsDialog from './NCBISettingsDialog'
13
12
  import { BASE_BLAST_URL } from './consts'
13
+ import { useLocalStorage } from '../../../utils/useLocalStorage'
14
14
 
15
15
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util'
16
16
 
@@ -1,7 +1,6 @@
1
1
  import useSWR from 'swr'
2
2
 
3
3
  import {
4
- clearAllCachedResults,
5
4
  deleteCachedResult,
6
5
  getAllCachedResults,
7
6
  } from '../../../utils/blastCache'
@@ -30,8 +29,11 @@ export function useCachedBlastResults(geneIds: string[]) {
30
29
  )
31
30
  }
32
31
 
32
+ // deletes only what this hook listed, i.e. the results for these gene ids.
33
+ // The list the user is looking at is gene-scoped, so a store-wide clear here
34
+ // would silently throw away every other gene's cached alignments too
33
35
  const handleClearAll = async () => {
34
- await clearAllCachedResults()
36
+ await Promise.all((results ?? []).map(r => deleteCachedResult(r.id)))
35
37
  await mutate([], false)
36
38
  }
37
39
 
@@ -1,35 +1,39 @@
1
- import {
2
- dedupe,
3
- defaultCodonTable,
4
- generateCodonTable,
5
- revcom,
6
- } from '@jbrowse/core/util'
1
+ import { dedupe, revcom } from '@jbrowse/core/util'
2
+ import { convertCodingSequenceToPeptides } from '@jbrowse/core/util/convertCodingSequenceToPeptides'
3
+
4
+ import { getGeneticCode, parseTranslTable } from './geneticCodes'
7
5
 
8
6
  import type { Feat } from './types'
9
7
  import type { Feature } from '@jbrowse/core/util'
10
8
 
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
-
15
- export function stitch(subfeats: Feat[], sequence: string) {
16
- return subfeats.map(sub => sequence.slice(sub.start, sub.end)).join('')
17
- }
9
+ // `@jbrowse/core/util/convertCodingSequenceToPeptides` is a deep path, so unlike
10
+ // the `@jbrowse/core/util` barrel it is absent from ReExports and gets bundled
11
+ // rather than resolved out of the host's JBrowseExports. That is what makes
12
+ // reusing core's translation safe across every host a config names: this module
13
+ // previously built its codon table at module scope from the barrel's
14
+ // `defaultCodonTable`, and a core build that dropped that export turned it into
15
+ // `Object.keys(undefined)` while the UMD was still evaluating -- the plugin
16
+ // global was never assigned and PluginLoader error-paged the whole app.
18
17
 
19
18
  export function calculateProteinSequence({
20
19
  cds,
21
20
  sequence,
21
+ geneticCodeId,
22
22
  }: {
23
23
  cds: Feat[]
24
24
  sequence: string
25
+ geneticCodeId?: number
25
26
  }) {
26
- const str = stitch(cds, sequence)
27
- let protein = ''
28
- for (let i = 0; i < str.length; i += 3) {
29
- // use & symbol for undefined codon, or partial slice
30
- protein += codonTable[str.slice(i, i + 3)] ?? '&'
31
- }
32
- return protein
27
+ // `starts` is deliberately not passed: @jbrowse/core 4.3.0's signature has no
28
+ // such parameter, so alternative initiators (GTG under table 11, ATA under
29
+ // table 2) render as their internal residue rather than M. Core main added it;
30
+ // pass it here when msaview's @jbrowse/core floor reaches that release.
31
+ const { codonTable } = getGeneticCode(geneticCodeId)
32
+ return convertCodingSequenceToPeptides({
33
+ cds,
34
+ sequence,
35
+ codonTable,
36
+ })
33
37
  }
34
38
 
35
39
  export function revlist(list: Feat[], seqlen: number) {
@@ -62,8 +66,19 @@ export function getProteinSequenceFromFeature({
62
66
  feat => `${feat.start}-${feat.end}`,
63
67
  )
64
68
 
69
+ // a mitochondrial gene declares e.g. transl_table=2, so it translates with
70
+ // NCBI table 2 rather than the standard code. GFF3 usually carries the
71
+ // attribute on the CDS rather than the transcript, so check both.
72
+ const cdsSubfeature = feature
73
+ .get('subfeatures')
74
+ ?.find((f: Feature) => f.get('type')?.toLowerCase() === 'cds')
75
+ const geneticCodeId =
76
+ parseTranslTable(feature.get('transl_table')) ??
77
+ parseTranslTable(cdsSubfeature?.get('transl_table'))
78
+
65
79
  return calculateProteinSequence({
66
80
  cds: strand === -1 ? revlist(cds, seq.length) : cds,
67
81
  sequence: strand === -1 ? revcom(seq) : seq,
82
+ geneticCodeId,
68
83
  })
69
84
  }