jbrowse-plugin-msaview 2.7.2 → 2.7.4

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 (64) hide show
  1. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +6 -5
  2. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +7 -1
  3. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.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/OrthologQuery/OrthologPanel.d.ts +8 -0
  7. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +86 -0
  8. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +9 -0
  9. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +13 -0
  10. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +8 -0
  11. package/dist/MsaViewPanel/afterCreateAutoruns.js +28 -0
  12. package/dist/MsaViewPanel/doLaunchBlast.js +10 -22
  13. package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +23 -0
  14. package/dist/MsaViewPanel/doLaunchOrthologs.js +97 -0
  15. package/dist/MsaViewPanel/genomeToMSA.js +13 -6
  16. package/dist/MsaViewPanel/genomeToMSA.test.js +35 -6
  17. package/dist/MsaViewPanel/model.d.ts +88 -70
  18. package/dist/MsaViewPanel/model.js +26 -13
  19. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +32 -13
  20. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +43 -14
  21. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +108 -18
  22. package/dist/MsaViewPanel/msaDataStore.js +8 -17
  23. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +8 -6
  24. package/dist/jbrowse-plugin-msaview.umd.production.min.js +35 -31
  25. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  26. package/dist/utils/blastCache.d.ts +1 -2
  27. package/dist/utils/blastCache.js +9 -22
  28. package/dist/utils/domainCache.js +6 -15
  29. package/dist/utils/idb.d.ts +12 -0
  30. package/dist/utils/idb.js +21 -0
  31. package/dist/utils/ncbiOrthologs.d.ts +105 -0
  32. package/dist/utils/ncbiOrthologs.js +211 -0
  33. package/dist/utils/ncbiOrthologs.test.d.ts +1 -0
  34. package/dist/utils/ncbiOrthologs.test.js +41 -0
  35. package/dist/utils/taxonomyNames.js +13 -18
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +3 -3
  39. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +6 -8
  40. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +13 -2
  41. package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +1 -1
  42. package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +4 -2
  43. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +172 -0
  44. package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +28 -0
  45. package/src/MsaViewPanel/afterCreateAutoruns.ts +27 -0
  46. package/src/MsaViewPanel/doLaunchBlast.ts +20 -29
  47. package/src/MsaViewPanel/doLaunchOrthologs.ts +123 -0
  48. package/src/MsaViewPanel/genomeToMSA.test.ts +38 -6
  49. package/src/MsaViewPanel/genomeToMSA.ts +14 -6
  50. package/src/MsaViewPanel/model.ts +41 -12
  51. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +117 -18
  52. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +70 -26
  53. package/src/MsaViewPanel/msaDataStore.ts +17 -17
  54. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +8 -6
  55. package/src/utils/blastCache.ts +20 -23
  56. package/src/utils/domainCache.ts +14 -18
  57. package/src/utils/idb.ts +28 -0
  58. package/src/utils/ncbiOrthologs.test.ts +56 -0
  59. package/src/utils/ncbiOrthologs.ts +320 -0
  60. package/src/utils/taxonomyNames.ts +22 -24
  61. package/src/version.ts +1 -1
  62. package/dist/MsaViewPanel/blosum62.d.ts +0 -2
  63. package/dist/MsaViewPanel/blosum62.js +0 -627
  64. package/src/MsaViewPanel/blosum62.ts +0 -628
@@ -1,5 +1,14 @@
1
+ import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
1
2
  import { describe, expect, test } from 'vitest';
2
- import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
3
+ import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
4
+ // codon at protein position i covers three consecutive genome bases starting at
5
+ // 100 + i * 3, i.e. a single-exon forward-strand transcript
6
+ function forwardCodons(n) {
7
+ return Object.fromEntries(Array.from({ length: n }, (_, i) => [
8
+ i,
9
+ [100 + i * 3, 101 + i * 3, 102 + i * 3],
10
+ ]));
11
+ }
3
12
  describe('msaCoordToGenomeCoord', () => {
4
13
  test('returns undefined when neither transcriptToMsaMap nor mafRegion is defined', () => {
5
14
  const model = {
@@ -16,7 +25,7 @@ describe('msaCoordToGenomeCoord', () => {
16
25
  querySeqName: 'QUERY',
17
26
  transcriptToMsaMap: {
18
27
  refName: 'chr1',
19
- p2g: { 0: 100, 1: 103 },
28
+ p2gCodon: forwardCodons(2),
20
29
  },
21
30
  rows: [['OTHER', 'MKAA']],
22
31
  };
@@ -28,7 +37,7 @@ describe('msaCoordToGenomeCoord', () => {
28
37
  querySeqName: 'QUERY',
29
38
  transcriptToMsaMap: {
30
39
  refName: 'chr1',
31
- p2g: { 0: 100, 1: 103 },
40
+ p2gCodon: forwardCodons(2),
32
41
  },
33
42
  rows: [['QUERY', 'M-KA']],
34
43
  };
@@ -41,7 +50,7 @@ describe('msaCoordToGenomeCoord', () => {
41
50
  querySeqName: 'QUERY',
42
51
  transcriptToMsaMap: {
43
52
  refName: 'chr1',
44
- p2g: { 0: 100, 1: 103, 2: 106, 3: 109 },
53
+ p2gCodon: forwardCodons(4),
45
54
  },
46
55
  rows: [['QUERY', 'MKAA']],
47
56
  };
@@ -58,7 +67,7 @@ describe('msaCoordToGenomeCoord', () => {
58
67
  querySeqName: 'QUERY',
59
68
  transcriptToMsaMap: {
60
69
  refName: 'chr1',
61
- p2g: { 0: 100, 1: 103, 2: 106, 3: 109 },
70
+ p2gCodon: forwardCodons(4),
62
71
  },
63
72
  rows: [['QUERY', 'M-K-AA']],
64
73
  // 012345 gapped positions
@@ -79,34 +88,33 @@ describe('msaCoordToGenomeCoord', () => {
79
88
  end: 109,
80
89
  });
81
90
  });
82
- test('returns undefined when p2g mapping is incomplete', () => {
91
+ test('returns undefined when the position has no codon mapping', () => {
83
92
  const model = {
84
93
  querySeqName: 'QUERY',
85
94
  transcriptToMsaMap: {
86
95
  refName: 'chr1',
87
- p2g: { 0: 100 }, // Missing entry for position 1
96
+ p2gCodon: forwardCodons(1),
88
97
  },
89
98
  rows: [['QUERY', 'MKAA']],
90
99
  };
91
- // Position 0 needs p2g[0] and p2g[1], but p2g[1] is missing
92
- const result = msaCoordToGenomeCoord({ model, coord: 0 });
100
+ // ungapped position 1 has no entry in p2gCodon
101
+ const result = msaCoordToGenomeCoord({ model, coord: 1 });
93
102
  expect(result).toBeUndefined();
94
103
  });
95
- test('handles reverse strand (start > end in p2g)', () => {
104
+ test('maps the final residue, whose codon has no successor', () => {
96
105
  const model = {
97
106
  querySeqName: 'QUERY',
98
107
  transcriptToMsaMap: {
99
108
  refName: 'chr1',
100
- p2g: { 0: 109, 1: 106, 2: 103, 3: 100 }, // Reverse strand
109
+ p2gCodon: forwardCodons(4),
101
110
  },
102
111
  rows: [['QUERY', 'MKAA']],
103
112
  };
104
- // Should return min/max correctly
105
- const result = msaCoordToGenomeCoord({ model, coord: 0 });
113
+ const result = msaCoordToGenomeCoord({ model, coord: 3 });
106
114
  expect(result).toEqual({
107
115
  refName: 'chr1',
108
- start: 106, // min(109, 106)
109
- end: 109, // max(109, 106)
116
+ start: 109,
117
+ end: 112,
110
118
  });
111
119
  });
112
120
  test('returns undefined for out of bounds coord', () => {
@@ -114,7 +122,7 @@ describe('msaCoordToGenomeCoord', () => {
114
122
  querySeqName: 'QUERY',
115
123
  transcriptToMsaMap: {
116
124
  refName: 'chr1',
117
- p2g: { 0: 100, 1: 103 },
125
+ p2gCodon: forwardCodons(2),
118
126
  },
119
127
  rows: [['QUERY', 'MK']],
120
128
  };
@@ -127,7 +135,7 @@ describe('msaCoordToGenomeCoord', () => {
127
135
  querySeqName: 'SEQ2',
128
136
  transcriptToMsaMap: {
129
137
  refName: 'chr1',
130
- p2g: { 0: 200, 1: 203 },
138
+ p2gCodon: { 0: [200, 201, 202], 1: [203, 204, 205] },
131
139
  },
132
140
  rows: [
133
141
  ['SEQ1', 'AAAA'],
@@ -142,6 +150,88 @@ describe('msaCoordToGenomeCoord', () => {
142
150
  end: 203,
143
151
  });
144
152
  });
153
+ // The mapping comes from the real g2p_mapper rather than hand-written
154
+ // fixtures: on the reverse strand p2g stores the codon's *highest*
155
+ // coordinate, which is what the old p2g[pos]..p2g[pos+1] arithmetic got wrong
156
+ describe('real g2p_mapper mappings', () => {
157
+ test('forward strand, single exon', () => {
158
+ const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
159
+ refName: 'chr1',
160
+ start: 100,
161
+ end: 112,
162
+ strand: 1,
163
+ subfeatures: [{ refName: 'chr1', type: 'CDS', start: 100, end: 112 }],
164
+ });
165
+ const model = {
166
+ querySeqName: 'QUERY',
167
+ transcriptToMsaMap: { refName, p2gCodon },
168
+ rows: [['QUERY', 'MKAA']],
169
+ };
170
+ expect(msaCoordToGenomeCoord({ model, coord: 0 })).toEqual({
171
+ refName: 'chr1',
172
+ start: 100,
173
+ end: 103,
174
+ });
175
+ expect(msaCoordToGenomeCoord({ model, coord: 3 })).toEqual({
176
+ refName: 'chr1',
177
+ start: 109,
178
+ end: 112,
179
+ });
180
+ });
181
+ test('reverse strand codon covers the last three bases of the CDS', () => {
182
+ const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
183
+ refName: 'chr1',
184
+ start: 100,
185
+ end: 112,
186
+ strand: -1,
187
+ subfeatures: [{ refName: 'chr1', type: 'CDS', start: 100, end: 112 }],
188
+ });
189
+ const model = {
190
+ querySeqName: 'QUERY',
191
+ transcriptToMsaMap: { refName, p2gCodon },
192
+ rows: [['QUERY', 'MKAA']],
193
+ };
194
+ // the first residue is translated from the 3' end of the genome region
195
+ expect(msaCoordToGenomeCoord({ model, coord: 0 })).toEqual({
196
+ refName: 'chr1',
197
+ start: 109,
198
+ end: 112,
199
+ });
200
+ expect(msaCoordToGenomeCoord({ model, coord: 3 })).toEqual({
201
+ refName: 'chr1',
202
+ start: 100,
203
+ end: 103,
204
+ });
205
+ });
206
+ test('codon split across an exon boundary yields one region per piece', () => {
207
+ // exon 1 contributes 4 bases, so residue 1 straddles the intron
208
+ const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
209
+ refName: 'chr1',
210
+ start: 100,
211
+ end: 210,
212
+ strand: 1,
213
+ subfeatures: [
214
+ { refName: 'chr1', type: 'CDS', start: 100, end: 104 },
215
+ { refName: 'chr1', type: 'CDS', start: 200, end: 202 },
216
+ ],
217
+ });
218
+ const model = {
219
+ querySeqName: 'QUERY',
220
+ transcriptToMsaMap: { refName, p2gCodon },
221
+ rows: [['QUERY', 'MK']],
222
+ };
223
+ expect(msaCoordToGenomeRegions({ model, coord: 1 })).toEqual([
224
+ { refName: 'chr1', start: 103, end: 104 },
225
+ { refName: 'chr1', start: 200, end: 202 },
226
+ ]);
227
+ // the single-region form bounds the pieces, for navigation
228
+ expect(msaCoordToGenomeCoord({ model, coord: 1 })).toEqual({
229
+ refName: 'chr1',
230
+ start: 103,
231
+ end: 202,
232
+ });
233
+ });
234
+ });
145
235
  // MAF region tests
146
236
  describe('mafRegion', () => {
147
237
  test('returns genome position for mafRegion mapping', () => {
@@ -217,7 +307,7 @@ describe('msaCoordToGenomeCoord', () => {
217
307
  querySeqName: 'hg38.chr1',
218
308
  transcriptToMsaMap: {
219
309
  refName: 'chr2',
220
- p2g: { 0: 5000, 1: 5003 },
310
+ p2gCodon: { 0: [5000, 5001, 5002], 1: [5003, 5004, 5005] },
221
311
  },
222
312
  mafRegion: {
223
313
  refName: 'chr1',
@@ -1,22 +1,13 @@
1
- import { openDB } from 'idb';
1
+ import { createDbOpener } from '../utils/idb';
2
2
  const DB_NAME = 'jbrowse-msaview-data';
3
3
  const DB_VERSION = 1;
4
4
  const STORE_NAME = 'msa-data';
5
- let dbPromise;
6
- function getDB() {
7
- dbPromise ??= openDB(DB_NAME, DB_VERSION, {
8
- upgrade(db) {
9
- if (!db.objectStoreNames.contains(STORE_NAME)) {
10
- const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
11
- store.createIndex('timestamp', 'timestamp', { unique: false });
12
- }
13
- },
14
- }).catch((e) => {
15
- dbPromise = undefined;
16
- throw e;
17
- });
18
- return dbPromise;
19
- }
5
+ const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
6
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
7
+ const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
8
+ store.createIndex('timestamp', 'timestamp', { unique: false });
9
+ }
10
+ });
20
11
  export function generateDataStoreId() {
21
12
  return `msa-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
22
13
  }
@@ -41,7 +32,7 @@ export async function storeMsaData(id, data) {
41
32
  export async function retrieveMsaData(id) {
42
33
  try {
43
34
  const db = await getDB();
44
- const result = (await db.get(STORE_NAME, id));
35
+ const result = await db.get(STORE_NAME, id);
45
36
  if (result) {
46
37
  return {
47
38
  msa: result.msa,
@@ -45,12 +45,14 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
45
45
  beforeEach(() => {
46
46
  vi.clearAllMocks();
47
47
  });
48
- test('genome hover at coord 1005 highlights MSA column 5', () => {
48
+ test('genome hover at coord 1005 highlights MSA column 4', () => {
49
49
  const { model, calls } = makeModel();
50
50
  const run = syncGenomeHoverToMsaColumn(model);
51
- hoverGenome(1005); // 1005 - mafRegion.start(1000) = ungapped 5
51
+ // the hover coord is 1-based, so 1005 is the 0-based base 1004, i.e.
52
+ // ungapped offset 4 into a region starting at 1000
53
+ hoverGenome(1005);
52
54
  run();
53
- expect(calls).toEqual([5]);
55
+ expect(calls).toEqual([4]);
54
56
  });
55
57
  test('moving the genome hover moves the highlighted column', () => {
56
58
  const { model, calls } = makeModel();
@@ -59,7 +61,7 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
59
61
  run();
60
62
  hoverGenome(1007);
61
63
  run();
62
- expect(calls).toEqual([2, 7]);
64
+ expect(calls).toEqual([1, 6]);
63
65
  });
64
66
  test('leaving the genome clears the column it set', () => {
65
67
  const { model, calls } = makeModel();
@@ -68,7 +70,7 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
68
70
  run();
69
71
  clearGenomeHover();
70
72
  run();
71
- expect(calls).toEqual([4, undefined]);
73
+ expect(calls).toEqual([3, undefined]);
72
74
  });
73
75
  test('a hover outside the maf region clears a previously-set column once', () => {
74
76
  const { model, calls } = makeModel();
@@ -78,7 +80,7 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
78
80
  hoverGenome(5000); // outside [1000,1010) -> genomeToMSA returns undefined
79
81
  run();
80
82
  run();
81
- expect(calls).toEqual([4, undefined]);
83
+ expect(calls).toEqual([3, undefined]);
82
84
  });
83
85
  test('never touches mouseCol when the genome never provides a column, so a direct MSA hover survives unrelated session hovers', () => {
84
86
  const { model, calls } = makeModel();