@cmdcolin/jbrowse-plugin-hubs 1.0.14 → 1.0.16

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.
@@ -0,0 +1 @@
1
+ export declare function getConfigUrls(assemblyName: string): string[];
@@ -0,0 +1,44 @@
1
+ function getGenArkConfigUrl(accession) {
2
+ const [base, rest] = accession.split('_');
3
+ if (!rest) {
4
+ return undefined;
5
+ }
6
+ const match = rest.match(/.{1,3}/g);
7
+ if (!match || match.length < 3) {
8
+ return undefined;
9
+ }
10
+ const [b1, b2, b3] = match;
11
+ return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`;
12
+ }
13
+ // Candidate urls for an assembly name, most preferred first. The connection
14
+ // probe and the describe lookup each take the first that exists, so a name with
15
+ // no hosted config anywhere is still answered by staying quiet.
16
+ //
17
+ // A UCSC db gets `minimal.json` ahead of `config.json`. Both carry the same
18
+ // `assemblies` block (minimal.json is a filtered copy of config.json, tracks
19
+ // dropped to NCBI RefSeq / GENCODE / RepeatMasker / gaps), and the assembly is
20
+ // the whole reason this connection exists — the tracks are a bonus for the
21
+ // panel it opens. The full config is an expensive way to learn a sequence
22
+ // adapter: hg38 is 2.1MB against minimal's 300KB, hg19 1.35MB against 57KB,
23
+ // hs1 603KB against 12KB, and the parse is followed by MST instantiating every
24
+ // track config in it on the main thread (hg38 595 against 33, hs1 624 against
25
+ // 7). hg38 alone names 239 distinct mate assemblies across its 239 synteny
26
+ // tracks, so a session that opens a few of them pays this several times over.
27
+ //
28
+ // What minimal.json costs is the mate genome's long tail — conservation,
29
+ // expression, the rest of the hub. Those are still one "Add connection" away
30
+ // on the full config url, and nothing about this panel implied they were
31
+ // coming. GenArk hubs have no minimal.json and don't need one; their whole
32
+ // config is ~40KB.
33
+ export function getConfigUrls(assemblyName) {
34
+ if (!/^[\w.]+$/.test(assemblyName)) {
35
+ return [];
36
+ }
37
+ if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
38
+ const url = getGenArkConfigUrl(assemblyName);
39
+ return url ? [url] : [];
40
+ }
41
+ const base = `https://jbrowse.org/ucsc/${assemblyName}`;
42
+ return [`${base}/minimal.json`, `${base}/config.json`];
43
+ }
44
+ //# sourceMappingURL=configUrls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configUrls.js","sourceRoot":"","sources":["../src/configUrls.ts"],"names":[],"mappings":"AAAA,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,CAAA;IAC1B,OAAO,mCAAmC,IAAI,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,SAAS,cAAc,CAAA;AAC7F,CAAC;AAED,4EAA4E;AAC5E,gFAAgF;AAChF,gEAAgE;AAChE,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,+EAA+E;AAC/E,2EAA2E;AAC3E,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,mBAAmB;AACnB,MAAM,UAAU,aAAa,CAAC,YAAoB;IAChD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,CAAA;IACX,CAAC;IACD,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,MAAM,GAAG,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAA;QAC5C,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACzB,CAAC;IACD,MAAM,IAAI,GAAG,4BAA4B,YAAY,EAAE,CAAA;IACvD,OAAO,CAAC,GAAG,IAAI,eAAe,EAAE,GAAG,IAAI,cAAc,CAAC,CAAA;AACxD,CAAC"}
@@ -0,0 +1,26 @@
1
+ interface Description {
2
+ assembly: HubAssembly;
3
+ geneAdapter?: Record<string, unknown>;
4
+ }
5
+ interface HubAssembly {
6
+ name: string;
7
+ aliases?: string[];
8
+ }
9
+ interface HubConfig {
10
+ assemblies?: HubAssembly[];
11
+ tracks?: {
12
+ trackId: string;
13
+ adapter?: Record<string, unknown>;
14
+ }[];
15
+ }
16
+ export declare function describeFromConfig(config: HubConfig, assemblyName: string, configUrl: string): Description | undefined;
17
+ /**
18
+ * Core-describeAssemblies: the assembly config and gene track adapter the
19
+ * hosted config of each named genome holds, read without connecting it. A
20
+ * name another plugin already described, or one no hosted config could hold,
21
+ * is left alone
22
+ */
23
+ export declare function describeAssemblies(described: unknown, assemblyNames: unknown): Promise<{
24
+ [k: string]: Description | undefined;
25
+ }>;
26
+ export {};
@@ -0,0 +1,91 @@
1
+ import { getConfigUrls } from './configUrls';
2
+ // jb2hubs' defaultGeneTrackId order, which also picks the track minimal.json
3
+ // keeps and a hub genome's default session opens
4
+ const GENE_TRACK_SUFFIXES = [
5
+ 'ncbiRefSeq',
6
+ 'ncbiRefSeqCurated',
7
+ 'ncbiGene',
8
+ 'refGene',
9
+ 'ensGene',
10
+ 'augustusGene',
11
+ 'xenoRefGene',
12
+ ];
13
+ // A TwoBit adapter's bare `chromSizes` resolves against the `baseUri` beside
14
+ // its `uri`, so rewriting each `uri` alone sent jb2hubs' relative chrom.sizes
15
+ // to the page's origin
16
+ function withBaseUri(value, base) {
17
+ if (Array.isArray(value)) {
18
+ return value.map(item => withBaseUri(item, base));
19
+ }
20
+ if (typeof value === 'object' && value !== null) {
21
+ const entries = Object.entries(value).map(([key, item]) => [
22
+ key,
23
+ withBaseUri(item, base),
24
+ ]);
25
+ return Object.fromEntries(typeof value.uri === 'string' &&
26
+ !('baseUri' in value)
27
+ ? [...entries, ['baseUri', base]]
28
+ : entries);
29
+ }
30
+ return value;
31
+ }
32
+ export function describeFromConfig(config, assemblyName, configUrl) {
33
+ const assembly = config.assemblies?.find(a => a.name === assemblyName || a.aliases?.includes(assemblyName));
34
+ if (!assembly) {
35
+ return undefined;
36
+ }
37
+ const tracks = new Map(config.tracks?.map(track => [track.trackId, track]));
38
+ const geneAdapter = GENE_TRACK_SUFFIXES.map(suffix => tracks.get(`${assembly.name}-${suffix}`)?.adapter).find(adapter => adapter !== undefined);
39
+ return {
40
+ assembly: withBaseUri(assembly, configUrl),
41
+ geneAdapter: geneAdapter && withBaseUri(geneAdapter, configUrl),
42
+ };
43
+ }
44
+ // throws on anything but a found config or a 404, so only a definitive
45
+ // answer is cached
46
+ async function fetchDescription(assemblyName) {
47
+ for (const url of getConfigUrls(assemblyName)) {
48
+ const response = await fetch(url);
49
+ if (response.ok) {
50
+ const config = (await response.json());
51
+ return describeFromConfig(config, assemblyName, url);
52
+ }
53
+ if (response.status !== 404) {
54
+ throw new Error(`${url}: HTTP ${response.status}`);
55
+ }
56
+ }
57
+ return undefined;
58
+ }
59
+ const descriptions = new Map();
60
+ function describe(assemblyName) {
61
+ let description = descriptions.get(assemblyName);
62
+ if (!description) {
63
+ description = fetchDescription(assemblyName).catch((error) => {
64
+ descriptions.delete(assemblyName);
65
+ console.error(error);
66
+ return undefined;
67
+ });
68
+ descriptions.set(assemblyName, description);
69
+ }
70
+ return description;
71
+ }
72
+ /**
73
+ * Core-describeAssemblies: the assembly config and gene track adapter the
74
+ * hosted config of each named genome holds, read without connecting it. A
75
+ * name another plugin already described, or one no hosted config could hold,
76
+ * is left alone
77
+ */
78
+ export async function describeAssemblies(described, assemblyNames) {
79
+ const before = typeof described === 'object' && described !== null ? described : {};
80
+ const names = Array.isArray(assemblyNames)
81
+ ? assemblyNames.filter((name) => typeof name === 'string')
82
+ : [];
83
+ const answers = await Promise.all(names
84
+ .filter(name => !Object.hasOwn(before, name) && getConfigUrls(name).length > 0)
85
+ .map(async (name) => [name, await describe(name)]));
86
+ return {
87
+ ...before,
88
+ ...Object.fromEntries(answers.filter(([, description]) => description !== undefined)),
89
+ };
90
+ }
91
+ //# sourceMappingURL=describeAssemblies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describeAssemblies.js","sourceRoot":"","sources":["../src/describeAssemblies.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
2
  import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util';
3
3
  import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree';
4
+ import { getConfigUrls } from './configUrls';
5
+ import { describeAssemblies } from './describeAssemblies';
4
6
  import { saveAs, toDesktopSnapshot } from './util';
5
7
  import { version } from '../package.json';
6
8
  // Serializes the current web session as a desktop `.jbrowse` file. genomes
@@ -11,46 +13,6 @@ function downloadDesktopSession(session) {
11
13
  const snap = toDesktopSnapshot(getSnapshot(jbrowse), getSnapshot(session));
12
14
  saveAs(new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }), 'session.jbrowse');
13
15
  }
14
- function getGenArkConfigUrl(accession) {
15
- const [base, rest] = accession.split('_');
16
- if (!rest) {
17
- return undefined;
18
- }
19
- const match = rest.match(/.{1,3}/g);
20
- if (!match || match.length < 3) {
21
- return undefined;
22
- }
23
- const [b1, b2, b3] = match;
24
- return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`;
25
- }
26
- // Candidate urls for an assembly name, most preferred first. The probe below
27
- // takes the first that exists, so a name with no hosted config anywhere is
28
- // still answered by staying quiet.
29
- //
30
- // A UCSC db gets `minimal.json` ahead of `config.json`. Both carry the same
31
- // `assemblies` block (minimal.json is a filtered copy of config.json, tracks
32
- // dropped to NCBI RefSeq / GENCODE / RepeatMasker / gaps), and the assembly is
33
- // the whole reason this connection exists — the tracks are a bonus for the
34
- // panel it opens. The full config is an expensive way to learn a sequence
35
- // adapter: hg38 is 2.1MB against minimal's 300KB, hg19 1.35MB against 57KB,
36
- // hs1 603KB against 12KB, and the parse is followed by MST instantiating every
37
- // track config in it on the main thread (hg38 595 against 33, hs1 624 against
38
- // 7). hg38 alone names 239 distinct mate assemblies across its 239 synteny
39
- // tracks, so a session that opens a few of them pays this several times over.
40
- //
41
- // What minimal.json costs is the mate genome's long tail — conservation,
42
- // expression, the rest of the hub. Those are still one "Add connection" away
43
- // on the full config url, and nothing about this panel implied they were
44
- // coming. GenArk hubs have no minimal.json and don't need one; their whole
45
- // config is ~40KB.
46
- function getConfigUrls(assemblyName) {
47
- if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
48
- const url = getGenArkConfigUrl(assemblyName);
49
- return url ? [url] : [];
50
- }
51
- const base = `https://jbrowse.org/ucsc/${assemblyName}`;
52
- return [`${base}/minimal.json`, `${base}/config.json`];
53
- }
54
16
  // Every assembly name this plugin has already taken a run at, whether the probe
55
17
  // is still in flight or came back with nothing. The extension point fires on an
56
18
  // unresolved read, and `get` is read from render paths, so a name that resolves
@@ -161,6 +123,10 @@ export default class HubsViewerPlugin extends Plugin {
161
123
  // returning undefined would clobber another plugin's result.
162
124
  return defaultResult;
163
125
  });
126
+ // A multi-way synteny lane whose genome the session lacks asks here for
127
+ // its assembly config and gene track. Cores older than the point never
128
+ // fire it, so this is inert on them
129
+ pluginManager.addToExtensionPoint('Core-describeAssemblies', (described, args) => describeAssemblies(described, args.assemblyNames));
164
130
  }
165
131
  configure(pluginManager) {
166
132
  // jbrowse-web only. In desktop you'd just save the session, and an exported
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";var JBrowsePluginHubs=(()=>{var v=Object.create;var a=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var I=Object.getPrototypeOf,T=Object.prototype.hasOwnProperty;var l=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),D=(n,e)=>{for(var t in e)a(n,t,{get:e[t],enumerable:!0})},m=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of $(e))!T.call(n,s)&&s!==t&&a(n,s,{get:()=>e[s],enumerable:!(o=E(e,s))||o.enumerable});return n};var g=(n,e,t)=>(t=n!=null?v(I(n)):{},m(e||!n||!n.__esModule?a(t,"default",{value:n,enumerable:!0}):t,n)),B=n=>m(a({},"__esModule",{value:!0}),n);var b=l((H,f)=>{f.exports=JBrowseExports["@jbrowse/core/Plugin"]});var k=l((z,w)=>{w.exports=JBrowseExports["@jbrowse/core/util"]});var j=l((F,h)=>{h.exports=JBrowseExports["mobx-state-tree"]});var L={};D(L,{default:()=>p});var C=g(b()),d=g(k()),c=g(j());function y(n,e){let t=URL.createObjectURL(n),o=document.createElement("a");o.href=t,o.download=e,o.rel="noopener",o.click(),setTimeout(()=>{URL.revokeObjectURL(t)},4e4)}function u(n){return Array.isArray(n)?n:[]}function x(n,e,t){let o=new Map;for(let s of[...n,...e])o.set(s[t],s);return[...o.values()]}function S(n,e){let{sessionTracks:t,sessionAssemblies:o,...s}=e;return{...n,tracks:x(u(n.tracks),u(t),"trackId"),assemblies:x(u(n.assemblies),u(o),"name"),defaultSession:s}}var A="1.0.14";function U(n){let{jbrowse:e}=(0,c.getRoot)(n),t=S((0,c.getSnapshot)(e),(0,c.getSnapshot)(n));y(new Blob([JSON.stringify(t,null,2)],{type:"application/json"}),"session.jbrowse")}function O(n){let[e,t]=n.split("_");if(!t)return;let o=t.match(/.{1,3}/g);if(!o||o.length<3)return;let[s,r,i]=o;return`https://jbrowse.org/hubs/genark/${e}/${s}/${r}/${i}/${n}/config.json`}function J(n){if(n.startsWith("GCA_")||n.startsWith("GCF_")){let t=O(n);return t?[t]:[]}let e=`https://jbrowse.org/ucsc/${n}`;return[`${e}/minimal.json`,`${e}/config.json`]}var R=new WeakMap;function _(n,e){let t=R.get(n);return t||(t=new Set,R.set(n,t)),t.has(e)?!1:(t.add(e),!0)}async function G(n,e,t){let o=`jb2hub-${e}`;if(!(n.connections.find(s=>s.connectionId===o)||!_(n,e))){for(let s of t)if((await fetch(s,{method:"HEAD"})).ok){let i={type:"JB2TrackHubConnection",uri:s,name:`conn_${e}`,assemblyNames:[e],connectionId:o};n.addConnectionConf(i),n.makeConnection(i,{silent:!0});return}}}var p=class extends C.default{name="HubsViewerPlugin";version=A;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(t,o)=>{let s=o.session,r=o.assemblyName,i=r?J(r):[];return s&&r&&i.length>0?G(s,r,i).catch(M=>{console.error(M)}):t})}configure(e){if(!d.isElectron&&(0,d.isAbstractMenuManager)(e.rootModel))try{e.rootModel.appendToMenu("Tools",{label:"Download desktop session (.jbrowse)",onClick:t=>{U(t)}})}catch(t){console.error("could not add the desktop-session download item",t)}}};return B(L);})();
1
+ "use strict";var JBrowsePluginHubs=(()=>{var I=Object.create;var u=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var g=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),H=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},w=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of O(e))!G.call(n,o)&&o!==t&&u(n,o,{get:()=>e[o],enumerable:!(s=U(e,o))||s.enumerable});return n};var m=(n,e,t)=>(t=n!=null?I(P(n)):{},w(e||!n||!n.__esModule?u(t,"default",{value:n,enumerable:!0}):t,n)),B=n=>w(u({},"__esModule",{value:!0}),n);var h=g((Q,k)=>{k.exports=JBrowseExports["@jbrowse/core/Plugin"]});var y=g((Y,j)=>{j.exports=JBrowseExports["@jbrowse/core/util"]});var x=g((Z,A)=>{A.exports=JBrowseExports["mobx-state-tree"]});var V={};H(V,{default:()=>l});var $=m(h()),f=m(y()),a=m(x());function _(n){let[e,t]=n.split("_");if(!t)return;let s=t.match(/.{1,3}/g);if(!s||s.length<3)return;let[o,r,i]=s;return`https://jbrowse.org/hubs/genark/${e}/${o}/${r}/${i}/${n}/config.json`}function c(n){if(!/^[\w.]+$/.test(n))return[];if(n.startsWith("GCA_")||n.startsWith("GCF_")){let t=_(n);return t?[t]:[]}let e=`https://jbrowse.org/ucsc/${n}`;return[`${e}/minimal.json`,`${e}/config.json`]}var v=["ncbiRefSeq","ncbiRefSeqCurated","ncbiGene","refGene","ensGene","augustusGene","xenoRefGene"];function d(n,e){if(Array.isArray(n))return n.map(t=>d(t,e));if(typeof n=="object"&&n!==null){let t=Object.entries(n).map(([s,o])=>[s,d(o,e)]);return Object.fromEntries(typeof n.uri=="string"&&!("baseUri"in n)?[...t,["baseUri",e]]:t)}return n}function J(n,e,t){let s=n.assemblies?.find(i=>i.name===e||i.aliases?.includes(e));if(!s)return;let o=new Map(n.tracks?.map(i=>[i.trackId,i])),r=v.map(i=>o.get(`${s.name}-${i}`)?.adapter).find(i=>i!==void 0);return{assembly:d(s,t),geneAdapter:r&&d(r,t)}}async function F(n){for(let e of c(n)){let t=await fetch(e);if(t.ok){let s=await t.json();return J(s,n,e)}if(t.status!==404)throw new Error(`${e}: HTTP ${t.status}`)}}var b=new Map;function L(n){let e=b.get(n);return e||(e=F(n).catch(t=>{b.delete(n),console.error(t)}),b.set(n,e)),e}async function C(n,e){let t=typeof n=="object"&&n!==null?n:{},s=Array.isArray(e)?e.filter(r=>typeof r=="string"):[],o=await Promise.all(s.filter(r=>!Object.hasOwn(t,r)&&c(r).length>0).map(async r=>[r,await L(r)]));return{...t,...Object.fromEntries(o.filter(([,r])=>r!==void 0))}}function R(n,e){let t=URL.createObjectURL(n),s=document.createElement("a");s.href=t,s.download=e,s.rel="noopener",s.click(),setTimeout(()=>{URL.revokeObjectURL(t)},4e4)}function p(n){return Array.isArray(n)?n:[]}function S(n,e,t){let s=new Map;for(let o of[...n,...e])s.set(o[t],o);return[...s.values()]}function T(n,e){let{sessionTracks:t,sessionAssemblies:s,...o}=e;return{...n,tracks:S(p(n.tracks),p(t),"trackId"),assemblies:S(p(n.assemblies),p(s),"name"),defaultSession:o}}var E="1.0.16";function q(n){let{jbrowse:e}=(0,a.getRoot)(n),t=T((0,a.getSnapshot)(e),(0,a.getSnapshot)(n));R(new Blob([JSON.stringify(t,null,2)],{type:"application/json"}),"session.jbrowse")}var M=new WeakMap;function z(n,e){let t=M.get(n);return t||(t=new Set,M.set(n,t)),t.has(e)?!1:(t.add(e),!0)}async function K(n,e,t){let s=`jb2hub-${e}`;if(!(n.connections.find(o=>o.connectionId===s)||!z(n,e))){for(let o of t)if((await fetch(o,{method:"HEAD"})).ok){let i={type:"JB2TrackHubConnection",uri:o,name:`conn_${e}`,assemblyNames:[e],connectionId:s};n.addConnectionConf(i),n.makeConnection(i,{silent:!0});return}}}var l=class extends $.default{name="HubsViewerPlugin";version=E;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(t,s)=>{let o=s.session,r=s.assemblyName,i=r?c(r):[];return o&&r&&i.length>0?K(o,r,i).catch(D=>{console.error(D)}):t}),e.addToExtensionPoint("Core-describeAssemblies",(t,s)=>C(t,s.assemblyNames))}configure(e){if(!f.isElectron&&(0,f.isAbstractMenuManager)(e.rootModel))try{e.rootModel.appendToMenu("Tools",{label:"Download desktop session (.jbrowse)",onClick:t=>{q(t)}})}catch(t){console.error("could not add the desktop-session download item",t)}}};return B(V);})();
2
2
  //# sourceMappingURL=jbrowse-plugin-hubs.umd.production.min.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["global-externals:@jbrowse/core/Plugin", "global-externals:@jbrowse/core/util", "global-externals:@jbrowse/mobx-state-tree", "../src/index.ts", "../src/util.ts", "../package.json"],
4
- "sourcesContent": ["module.exports = JBrowseExports[\"@jbrowse/core/Plugin\"];", "module.exports = JBrowseExports[\"@jbrowse/core/util\"];", "module.exports = JBrowseExports[\"mobx-state-tree\"];", "import Plugin from '@jbrowse/core/Plugin'\nimport { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'\nimport { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'\n\nimport { saveAs, toDesktopSnapshot } from './util'\nimport { version } from '../package.json'\n\nimport type PluginManager from '@jbrowse/core/PluginManager'\nimport type { AbstractSessionModel } from '@jbrowse/core/util'\nimport type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree'\n\ninterface Session {\n makeConnection: (conf: unknown, initialSnapshot?: unknown) => void\n addConnectionConf: (conf: unknown) => void\n connections: { connectionId: string }[]\n}\n\n// Serializes the current web session as a desktop `.jbrowse` file. genomes\n// sessions are config-shaped already, so this just folds the live session into\n// a defaultSession and downloads it; desktop opens it via File > Open session.\nfunction downloadDesktopSession(session: AbstractSessionModel) {\n const { jbrowse } = getRoot<{ jbrowse: IAnyStateTreeNode }>(session)\n const snap = toDesktopSnapshot(\n getSnapshot<Record<string, unknown>>(jbrowse),\n getSnapshot<Record<string, unknown>>(session),\n )\n saveAs(\n new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),\n 'session.jbrowse',\n )\n}\n\nfunction getGenArkConfigUrl(accession: string) {\n const [base, rest] = accession.split('_')\n if (!rest) {\n return undefined\n }\n const match = rest.match(/.{1,3}/g)\n if (!match || match.length < 3) {\n return undefined\n }\n const [b1, b2, b3] = match\n return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`\n}\n\n// Candidate urls for an assembly name, most preferred first. The probe below\n// takes the first that exists, so a name with no hosted config anywhere is\n// still answered by staying quiet.\n//\n// A UCSC db gets `minimal.json` ahead of `config.json`. Both carry the same\n// `assemblies` block (minimal.json is a filtered copy of config.json, tracks\n// dropped to NCBI RefSeq / GENCODE / RepeatMasker / gaps), and the assembly is\n// the whole reason this connection exists \u2014 the tracks are a bonus for the\n// panel it opens. The full config is an expensive way to learn a sequence\n// adapter: hg38 is 2.1MB against minimal's 300KB, hg19 1.35MB against 57KB,\n// hs1 603KB against 12KB, and the parse is followed by MST instantiating every\n// track config in it on the main thread (hg38 595 against 33, hs1 624 against\n// 7). hg38 alone names 239 distinct mate assemblies across its 239 synteny\n// tracks, so a session that opens a few of them pays this several times over.\n//\n// What minimal.json costs is the mate genome's long tail \u2014 conservation,\n// expression, the rest of the hub. Those are still one \"Add connection\" away\n// on the full config url, and nothing about this panel implied they were\n// coming. GenArk hubs have no minimal.json and don't need one; their whole\n// config is ~40KB.\nfunction getConfigUrls(assemblyName: string) {\n if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {\n const url = getGenArkConfigUrl(assemblyName)\n return url ? [url] : []\n }\n const base = `https://jbrowse.org/ucsc/${assemblyName}`\n return [`${base}/minimal.json`, `${base}/config.json`]\n}\n\n// Every assembly name this plugin has already taken a run at, whether the probe\n// is still in flight or came back with nothing. The extension point fires on an\n// unresolved read, and `get` is read from render paths, so a name that resolves\n// to no hosted config is asked about again and again \u2014 GenArk strain hubs\n// (mouseStrains and friends) live under /hubs/genark/<hub>/<strain>/ and are not\n// at the url guessed from the name, and a session can name an assembly this host\n// has never heard of at all. Clearing this once the probe settled (what an\n// earlier `finally` did) meant the next read re-probed, i.e. one HEAD per render\n// forever for exactly the names nothing can supply.\n//\n// A name that DID resolve is also in here, and is separately deduped by\n// connectionId once the connection exists. Nothing is ever removed within a\n// session: a probe answers a question about a url that does not change within a\n// page load.\n//\n// Keyed by session, because the connection a probe created belongs to the\n// session and goes with it. A flat Set would remember a name across\n// `setSession` and leave the new session unable to resolve a genome the old one\n// had. WeakMap so a replaced session is still collectable.\n//\n// jbrowse-core@main reports each unknown name to the extension point once per\n// session, which makes this redundant there \u2014 but this plugin runs against\n// whatever core the host page shipped, down to the v4.0.0 floor, where it is\n// the only thing standing between a mistyped assembly name and an unbounded\n// request stream.\nconst attempted = new WeakMap<Session, Set<string>>()\n\nfunction markAttempted(session: Session, assemblyName: string) {\n let names = attempted.get(session)\n if (!names) {\n names = new Set()\n attempted.set(session, names)\n }\n if (names.has(assemblyName)) {\n return false\n }\n names.add(assemblyName)\n return true\n}\n\n// Connect to the first of `uris` that exists. Connecting to a config that 404s\n// puts a red error snackbar over a session that is otherwise working, so probe\n// first and stay quiet when there's nothing there \u2014 some other handler, or the\n// session itself, may be supplying the assembly.\nasync function connectIfConfigExists(\n session: Session,\n assemblyName: string,\n uris: string[],\n) {\n const connectionId = `jb2hub-${assemblyName}`\n if (\n session.connections.find(f => f.connectionId === connectionId) ||\n !markAttempted(session, assemblyName)\n ) {\n return\n }\n for (const uri of uris) {\n const response = await fetch(uri, { method: 'HEAD' })\n if (response.ok) {\n const conf = {\n type: 'JB2TrackHubConnection',\n uri,\n name: `conn_${assemblyName}`,\n assemblyNames: [assemblyName],\n connectionId,\n }\n session.addConnectionConf(conf)\n // `silent`: no \"Successfully loaded\" snackbar. Nobody asked for this\n // connection \u2014 it exists because a track referenced an assembly the\n // session didn't have \u2014 so announcing it is noise over whatever the\n // user was doing, and it lands on top of any screenshot of a hub\n // genome. The property is newer than every released core (it landed\n // after v4.3.0) and this plugin runs against whatever core the host\n // page shipped, down to the v4.0.0 floor, but passing it is safe\n // regardless: MST drops an undeclared key off a model snapshot rather\n // than rejecting it (verified against the fork \u2014 the instance is\n // created, the key is neither readable on it nor in its snapshot), so\n // an older core makes the same connection it always did and keeps its\n // toast.\n session.makeConnection(conf, { silent: true })\n return\n }\n }\n}\n\nexport default class HubsViewerPlugin extends Plugin {\n name = 'HubsViewerPlugin'\n version = version\n\n install(pluginManager: PluginManager) {\n pluginManager.addToExtensionPoint(\n 'Core-handleUnrecognizedAssembly',\n (defaultResult, args) => {\n const session = args.session as Session | undefined\n const assemblyName = args.assemblyName as string | undefined\n const uris = assemblyName ? getConfigUrls(assemblyName) : []\n if (session && assemblyName && uris.length > 0) {\n // The probe and the connection it may create land after this returns,\n // which is fine because assemblyManager re-reads reactively once the\n // assembly shows up.\n //\n // Returning the promise rather than the accumulator: a core that\n // knows to look (jbrowse-core > 4.3.0) awaits it in waitForAssembly,\n // which is what lets that wait end on an event instead of a 10s\n // timeout. Nothing observable happens between being asked and the\n // HEAD coming back, so a core with only the session to watch has to\n // guess, and 10s is both too long to fail in and too short for a cold\n // CDN. Older cores fold the return value into an accumulator they\n // then discard, so this is inert there.\n //\n // Nothing else registers on this point (checked across the plugin\n // ecosystem), so replacing the accumulator costs no other plugin its\n // result; if that changes, a handler registered after this one still\n // receives and can pass on whatever it is given.\n return connectIfConfigExists(session, assemblyName, uris).catch(\n (e: unknown) => {\n console.error(e)\n },\n )\n }\n // Pass the accumulator through: this extension point chains handlers, so\n // returning undefined would clobber another plugin's result.\n return defaultResult\n },\n )\n }\n\n configure(pluginManager: PluginManager) {\n // jbrowse-web only. In desktop you'd just save the session, and an exported\n // .jbrowse file carries this plugin in its plugins list, so an older desktop\n // would otherwise load and run this menu code when opening that file.\n if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {\n // 'Tools', not 'File'. Every released core from v4.0.0 to latest defines\n // the File menu as a thunk and appends with `menu.menuItems.push()`, which\n // throws on a function; the throw escapes configure() and renders the app\n // as an error page rather than costing just this menu item. Because the\n // plugin store serves `latest/` no-cache, that took out every\n // jbrowse.org/ucsc launch on hg38/hg19/mm39/hs1 the moment 1.0.9 shipped.\n // core@main resolves item contributions lazily and accepts either form,\n // but this plugin has to keep working on the releases already in the wild.\n //\n // Belt and braces on the same reasoning: a cosmetic menu item should never\n // be able to cost a user their whole session.\n try {\n pluginManager.rootModel.appendToMenu('Tools', {\n label: 'Download desktop session (.jbrowse)',\n onClick: (session: AbstractSessionModel) => {\n downloadDesktopSession(session)\n },\n })\n } catch (e) {\n console.error('could not add the desktop-session download item', e)\n }\n }\n }\n}\n", "// Trigger a browser download of `blob` as `filename`. Mirrors the anchor-download\n// path of @jbrowse/core's saveAs: the deferred revoke avoids canceling the\n// download before the browser has read the blob (the read is async in some\n// browsers). Inlined until a published core release exports saveAs from its util.\nexport function saveAs(blob: Blob, filename: string) {\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename\n anchor.rel = 'noopener'\n anchor.click()\n setTimeout(() => {\n URL.revokeObjectURL(url)\n }, 40000)\n}\n\nfunction asObjArray(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n}\n\n// Merges two lists of config objects by an id key, with `overrides` winning so\n// a session-scoped track/assembly that shadows a config one (same id) replaces\n// it rather than duplicating.\nfunction mergeById(\n base: Record<string, unknown>[],\n overrides: Record<string, unknown>[],\n key: string,\n): Record<string, unknown>[] {\n const merged = new Map<unknown, Record<string, unknown>>()\n for (const item of [...base, ...overrides]) {\n merged.set(item[key], item)\n }\n return [...merged.values()]\n}\n\n// Builds a desktop `.jbrowse` snapshot (a config plus a defaultSession) from a\n// web root config and its live session. Session-scoped tracks/assemblies are\n// hoisted into the config arrays so they open as first-class config entries in\n// desktop; the rest of the session becomes the defaultSession. Web sessions are\n// always UriLocation-based, so the result is fully portable to desktop.\nexport function toDesktopSnapshot(\n jbrowse: Record<string, unknown>,\n session: Record<string, unknown>,\n): Record<string, unknown> {\n const { sessionTracks, sessionAssemblies, ...defaultSession } = session\n return {\n ...jbrowse,\n tracks: mergeById(\n asObjArray(jbrowse.tracks),\n asObjArray(sessionTracks),\n 'trackId',\n ),\n assemblies: mergeById(\n asObjArray(jbrowse.assemblies),\n asObjArray(sessionAssemblies),\n 'name',\n ),\n defaultSession,\n }\n}\n", "{\n \"version\": \"1.0.14\",\n \"license\": \"MIT\",\n \"name\": \"@cmdcolin/jbrowse-plugin-hubs\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/GMOD/jbrowse-plugin-hubs.git\"\n },\n \"keywords\": [\n \"jbrowse\",\n \"jbrowse2\"\n ],\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.esm.js\",\n \"typings\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"start\": \"node esbuild.mjs --watch\",\n \"test\": \"echo 'no tests'\",\n \"format\": \"pnpm prettier --write .\",\n \"prebuild\": \"pnpm clean\",\n \"build\": \"tsc && NODE_ENV=production node esbuild.mjs\",\n \"lint\": \"eslint src --report-unused-disable-directives --max-warnings 0\",\n \"prepack\": \"pnpm build\",\n \"postversion\": \"git push --follow-tags\",\n \"preversion\": \"pnpm lint && pnpm build && pnpm host-compat\",\n \"host-compat\": \"node scripts/host-compat-probe.mjs --bundle dist/jbrowse-plugin-hubs.umd.production.min.js\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@fal-works/esbuild-plugin-global-externals\": \"^2.1.2\",\n \"@jbrowse/core\": \"^4.3.0\",\n \"@jbrowse/mobx-state-tree\": \"^5.11.1\",\n \"@mui/material\": \"^7.3.11\",\n \"@mui/system\": \"^7.3.11\",\n \"@mui/x-data-grid\": \"^8.29.1\",\n \"@types/node\": \"^25.9.3\",\n \"@types/react\": \"^19.2.17\",\n \"@typescript-eslint/eslint-plugin\": \"^8.61.1\",\n \"@typescript-eslint/parser\": \"^8.61.1\",\n \"esbuild\": \"^0.27.7\",\n \"eslint\": \"^10.5.0\",\n \"eslint-plugin-import-x\": \"^4.16.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.1.1\",\n \"eslint-plugin-unicorn\": \"^64.0.0\",\n \"mobx\": \"^6.16.1\",\n \"mobx-react\": \"^9.2.2\",\n \"prettier\": \"^3.8.4\",\n \"pretty-bytes\": \"^7.1.0\",\n \"puppeteer-core\": \"^25.4.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"rimraf\": \"^6.1.3\",\n \"rxjs\": \"^7.8.2\",\n \"serve\": \"^14.2.6\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.61.1\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["require_Plugin", "__commonJSMin", "exports", "module", "require_util", "__commonJSMin", "exports", "module", "require_mobx_state_tree", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "import_Plugin", "import_util", "import_mobx_state_tree", "saveAs", "blob", "filename", "url", "anchor", "asObjArray", "value", "mergeById", "base", "overrides", "key", "merged", "item", "toDesktopSnapshot", "jbrowse", "session", "sessionTracks", "sessionAssemblies", "defaultSession", "version", "downloadDesktopSession", "session", "jbrowse", "snap", "toDesktopSnapshot", "saveAs", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrls", "assemblyName", "url", "attempted", "markAttempted", "names", "connectIfConfigExists", "uris", "connectionId", "f", "uri", "conf", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "defaultResult", "args", "e"]
3
+ "sources": ["global-externals:@jbrowse/core/Plugin", "global-externals:@jbrowse/core/util", "global-externals:@jbrowse/mobx-state-tree", "../src/index.ts", "../src/configUrls.ts", "../src/describeAssemblies.ts", "../src/util.ts", "../package.json"],
4
+ "sourcesContent": ["module.exports = JBrowseExports[\"@jbrowse/core/Plugin\"];", "module.exports = JBrowseExports[\"@jbrowse/core/util\"];", "module.exports = JBrowseExports[\"mobx-state-tree\"];", "import Plugin from '@jbrowse/core/Plugin'\nimport { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'\nimport { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'\n\nimport { getConfigUrls } from './configUrls'\nimport { describeAssemblies } from './describeAssemblies'\nimport { saveAs, toDesktopSnapshot } from './util'\nimport { version } from '../package.json'\n\nimport type PluginManager from '@jbrowse/core/PluginManager'\nimport type { AbstractSessionModel } from '@jbrowse/core/util'\nimport type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree'\n\ninterface Session {\n makeConnection: (conf: unknown, initialSnapshot?: unknown) => void\n addConnectionConf: (conf: unknown) => void\n connections: { connectionId: string }[]\n}\n\n// Serializes the current web session as a desktop `.jbrowse` file. genomes\n// sessions are config-shaped already, so this just folds the live session into\n// a defaultSession and downloads it; desktop opens it via File > Open session.\nfunction downloadDesktopSession(session: AbstractSessionModel) {\n const { jbrowse } = getRoot<{ jbrowse: IAnyStateTreeNode }>(session)\n const snap = toDesktopSnapshot(\n getSnapshot<Record<string, unknown>>(jbrowse),\n getSnapshot<Record<string, unknown>>(session),\n )\n saveAs(\n new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),\n 'session.jbrowse',\n )\n}\n\n// Every assembly name this plugin has already taken a run at, whether the probe\n// is still in flight or came back with nothing. The extension point fires on an\n// unresolved read, and `get` is read from render paths, so a name that resolves\n// to no hosted config is asked about again and again \u2014 GenArk strain hubs\n// (mouseStrains and friends) live under /hubs/genark/<hub>/<strain>/ and are not\n// at the url guessed from the name, and a session can name an assembly this host\n// has never heard of at all. Clearing this once the probe settled (what an\n// earlier `finally` did) meant the next read re-probed, i.e. one HEAD per render\n// forever for exactly the names nothing can supply.\n//\n// A name that DID resolve is also in here, and is separately deduped by\n// connectionId once the connection exists. Nothing is ever removed within a\n// session: a probe answers a question about a url that does not change within a\n// page load.\n//\n// Keyed by session, because the connection a probe created belongs to the\n// session and goes with it. A flat Set would remember a name across\n// `setSession` and leave the new session unable to resolve a genome the old one\n// had. WeakMap so a replaced session is still collectable.\n//\n// jbrowse-core@main reports each unknown name to the extension point once per\n// session, which makes this redundant there \u2014 but this plugin runs against\n// whatever core the host page shipped, down to the v4.0.0 floor, where it is\n// the only thing standing between a mistyped assembly name and an unbounded\n// request stream.\nconst attempted = new WeakMap<Session, Set<string>>()\n\nfunction markAttempted(session: Session, assemblyName: string) {\n let names = attempted.get(session)\n if (!names) {\n names = new Set()\n attempted.set(session, names)\n }\n if (names.has(assemblyName)) {\n return false\n }\n names.add(assemblyName)\n return true\n}\n\n// Connect to the first of `uris` that exists. Connecting to a config that 404s\n// puts a red error snackbar over a session that is otherwise working, so probe\n// first and stay quiet when there's nothing there \u2014 some other handler, or the\n// session itself, may be supplying the assembly.\nasync function connectIfConfigExists(\n session: Session,\n assemblyName: string,\n uris: string[],\n) {\n const connectionId = `jb2hub-${assemblyName}`\n if (\n session.connections.find(f => f.connectionId === connectionId) ||\n !markAttempted(session, assemblyName)\n ) {\n return\n }\n for (const uri of uris) {\n const response = await fetch(uri, { method: 'HEAD' })\n if (response.ok) {\n const conf = {\n type: 'JB2TrackHubConnection',\n uri,\n name: `conn_${assemblyName}`,\n assemblyNames: [assemblyName],\n connectionId,\n }\n session.addConnectionConf(conf)\n // `silent`: no \"Successfully loaded\" snackbar. Nobody asked for this\n // connection \u2014 it exists because a track referenced an assembly the\n // session didn't have \u2014 so announcing it is noise over whatever the\n // user was doing, and it lands on top of any screenshot of a hub\n // genome. The property is newer than every released core (it landed\n // after v4.3.0) and this plugin runs against whatever core the host\n // page shipped, down to the v4.0.0 floor, but passing it is safe\n // regardless: MST drops an undeclared key off a model snapshot rather\n // than rejecting it (verified against the fork \u2014 the instance is\n // created, the key is neither readable on it nor in its snapshot), so\n // an older core makes the same connection it always did and keeps its\n // toast.\n session.makeConnection(conf, { silent: true })\n return\n }\n }\n}\n\nexport default class HubsViewerPlugin extends Plugin {\n name = 'HubsViewerPlugin'\n version = version\n\n install(pluginManager: PluginManager) {\n pluginManager.addToExtensionPoint(\n 'Core-handleUnrecognizedAssembly',\n (defaultResult, args) => {\n const session = args.session as Session | undefined\n const assemblyName = args.assemblyName as string | undefined\n const uris = assemblyName ? getConfigUrls(assemblyName) : []\n if (session && assemblyName && uris.length > 0) {\n // The probe and the connection it may create land after this returns,\n // which is fine because assemblyManager re-reads reactively once the\n // assembly shows up.\n //\n // Returning the promise rather than the accumulator: a core that\n // knows to look (jbrowse-core > 4.3.0) awaits it in waitForAssembly,\n // which is what lets that wait end on an event instead of a 10s\n // timeout. Nothing observable happens between being asked and the\n // HEAD coming back, so a core with only the session to watch has to\n // guess, and 10s is both too long to fail in and too short for a cold\n // CDN. Older cores fold the return value into an accumulator they\n // then discard, so this is inert there.\n //\n // Nothing else registers on this point (checked across the plugin\n // ecosystem), so replacing the accumulator costs no other plugin its\n // result; if that changes, a handler registered after this one still\n // receives and can pass on whatever it is given.\n return connectIfConfigExists(session, assemblyName, uris).catch(\n (e: unknown) => {\n console.error(e)\n },\n )\n }\n // Pass the accumulator through: this extension point chains handlers, so\n // returning undefined would clobber another plugin's result.\n return defaultResult\n },\n )\n // A multi-way synteny lane whose genome the session lacks asks here for\n // its assembly config and gene track. Cores older than the point never\n // fire it, so this is inert on them\n pluginManager.addToExtensionPoint(\n 'Core-describeAssemblies',\n (described: unknown, args) =>\n describeAssemblies(described, args.assemblyNames),\n )\n }\n\n configure(pluginManager: PluginManager) {\n // jbrowse-web only. In desktop you'd just save the session, and an exported\n // .jbrowse file carries this plugin in its plugins list, so an older desktop\n // would otherwise load and run this menu code when opening that file.\n if (!isElectron && isAbstractMenuManager(pluginManager.rootModel)) {\n // 'Tools', not 'File'. Every released core from v4.0.0 to latest defines\n // the File menu as a thunk and appends with `menu.menuItems.push()`, which\n // throws on a function; the throw escapes configure() and renders the app\n // as an error page rather than costing just this menu item. Because the\n // plugin store serves `latest/` no-cache, that took out every\n // jbrowse.org/ucsc launch on hg38/hg19/mm39/hs1 the moment 1.0.9 shipped.\n // core@main resolves item contributions lazily and accepts either form,\n // but this plugin has to keep working on the releases already in the wild.\n //\n // Belt and braces on the same reasoning: a cosmetic menu item should never\n // be able to cost a user their whole session.\n try {\n pluginManager.rootModel.appendToMenu('Tools', {\n label: 'Download desktop session (.jbrowse)',\n onClick: (session: AbstractSessionModel) => {\n downloadDesktopSession(session)\n },\n })\n } catch (e) {\n console.error('could not add the desktop-session download item', e)\n }\n }\n }\n}\n", "function getGenArkConfigUrl(accession: string) {\n const [base, rest] = accession.split('_')\n if (!rest) {\n return undefined\n }\n const match = rest.match(/.{1,3}/g)\n if (!match || match.length < 3) {\n return undefined\n }\n const [b1, b2, b3] = match\n return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`\n}\n\n// Candidate urls for an assembly name, most preferred first. The connection\n// probe and the describe lookup each take the first that exists, so a name with\n// no hosted config anywhere is still answered by staying quiet.\n//\n// A UCSC db gets `minimal.json` ahead of `config.json`. Both carry the same\n// `assemblies` block (minimal.json is a filtered copy of config.json, tracks\n// dropped to NCBI RefSeq / GENCODE / RepeatMasker / gaps), and the assembly is\n// the whole reason this connection exists \u2014 the tracks are a bonus for the\n// panel it opens. The full config is an expensive way to learn a sequence\n// adapter: hg38 is 2.1MB against minimal's 300KB, hg19 1.35MB against 57KB,\n// hs1 603KB against 12KB, and the parse is followed by MST instantiating every\n// track config in it on the main thread (hg38 595 against 33, hs1 624 against\n// 7). hg38 alone names 239 distinct mate assemblies across its 239 synteny\n// tracks, so a session that opens a few of them pays this several times over.\n//\n// What minimal.json costs is the mate genome's long tail \u2014 conservation,\n// expression, the rest of the hub. Those are still one \"Add connection\" away\n// on the full config url, and nothing about this panel implied they were\n// coming. GenArk hubs have no minimal.json and don't need one; their whole\n// config is ~40KB.\nexport function getConfigUrls(assemblyName: string) {\n if (!/^[\\w.]+$/.test(assemblyName)) {\n return []\n }\n if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {\n const url = getGenArkConfigUrl(assemblyName)\n return url ? [url] : []\n }\n const base = `https://jbrowse.org/ucsc/${assemblyName}`\n return [`${base}/minimal.json`, `${base}/config.json`]\n}\n", "import { getConfigUrls } from './configUrls'\n\ninterface Description {\n assembly: HubAssembly\n geneAdapter?: Record<string, unknown>\n}\n\ninterface HubAssembly {\n name: string\n aliases?: string[]\n}\n\ninterface HubConfig {\n assemblies?: HubAssembly[]\n tracks?: { trackId: string; adapter?: Record<string, unknown> }[]\n}\n\n// jb2hubs' defaultGeneTrackId order, which also picks the track minimal.json\n// keeps and a hub genome's default session opens\nconst GENE_TRACK_SUFFIXES = [\n 'ncbiRefSeq',\n 'ncbiRefSeqCurated',\n 'ncbiGene',\n 'refGene',\n 'ensGene',\n 'augustusGene',\n 'xenoRefGene',\n]\n\n// A TwoBit adapter's bare `chromSizes` resolves against the `baseUri` beside\n// its `uri`, so rewriting each `uri` alone sent jb2hubs' relative chrom.sizes\n// to the page's origin\nfunction withBaseUri<T>(value: T, base: string): T {\n if (Array.isArray(value)) {\n return value.map(item => withBaseUri(item, base)) as T\n }\n if (typeof value === 'object' && value !== null) {\n const entries = Object.entries(value).map(([key, item]) => [\n key,\n withBaseUri(item, base),\n ])\n return Object.fromEntries(\n typeof (value as { uri?: unknown }).uri === 'string' &&\n !('baseUri' in value)\n ? [...entries, ['baseUri', base]]\n : entries,\n ) as T\n }\n return value\n}\n\nexport function describeFromConfig(\n config: HubConfig,\n assemblyName: string,\n configUrl: string,\n): Description | undefined {\n const assembly = config.assemblies?.find(\n a => a.name === assemblyName || a.aliases?.includes(assemblyName),\n )\n if (!assembly) {\n return undefined\n }\n const tracks = new Map(config.tracks?.map(track => [track.trackId, track]))\n const geneAdapter = GENE_TRACK_SUFFIXES.map(\n suffix => tracks.get(`${assembly.name}-${suffix}`)?.adapter,\n ).find(adapter => adapter !== undefined)\n return {\n assembly: withBaseUri(assembly, configUrl),\n geneAdapter: geneAdapter && withBaseUri(geneAdapter, configUrl),\n }\n}\n\n// throws on anything but a found config or a 404, so only a definitive\n// answer is cached\nasync function fetchDescription(assemblyName: string) {\n for (const url of getConfigUrls(assemblyName)) {\n const response = await fetch(url)\n if (response.ok) {\n const config = (await response.json()) as HubConfig\n return describeFromConfig(config, assemblyName, url)\n }\n if (response.status !== 404) {\n throw new Error(`${url}: HTTP ${response.status}`)\n }\n }\n return undefined\n}\n\nconst descriptions = new Map<string, Promise<Description | undefined>>()\n\nfunction describe(assemblyName: string) {\n let description = descriptions.get(assemblyName)\n if (!description) {\n description = fetchDescription(assemblyName).catch((error: unknown) => {\n descriptions.delete(assemblyName)\n console.error(error)\n return undefined\n })\n descriptions.set(assemblyName, description)\n }\n return description\n}\n\n/**\n * Core-describeAssemblies: the assembly config and gene track adapter the\n * hosted config of each named genome holds, read without connecting it. A\n * name another plugin already described, or one no hosted config could hold,\n * is left alone\n */\nexport async function describeAssemblies(\n described: unknown,\n assemblyNames: unknown,\n) {\n const before =\n typeof described === 'object' && described !== null ? described : {}\n const names = Array.isArray(assemblyNames)\n ? assemblyNames.filter((name): name is string => typeof name === 'string')\n : []\n const answers = await Promise.all(\n names\n .filter(\n name => !Object.hasOwn(before, name) && getConfigUrls(name).length > 0,\n )\n .map(async name => [name, await describe(name)] as const),\n )\n return {\n ...before,\n ...Object.fromEntries(\n answers.filter(([, description]) => description !== undefined),\n ),\n }\n}\n", "// Trigger a browser download of `blob` as `filename`. Mirrors the anchor-download\n// path of @jbrowse/core's saveAs: the deferred revoke avoids canceling the\n// download before the browser has read the blob (the read is async in some\n// browsers). Inlined until a published core release exports saveAs from its util.\nexport function saveAs(blob: Blob, filename: string) {\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename\n anchor.rel = 'noopener'\n anchor.click()\n setTimeout(() => {\n URL.revokeObjectURL(url)\n }, 40000)\n}\n\nfunction asObjArray(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n}\n\n// Merges two lists of config objects by an id key, with `overrides` winning so\n// a session-scoped track/assembly that shadows a config one (same id) replaces\n// it rather than duplicating.\nfunction mergeById(\n base: Record<string, unknown>[],\n overrides: Record<string, unknown>[],\n key: string,\n): Record<string, unknown>[] {\n const merged = new Map<unknown, Record<string, unknown>>()\n for (const item of [...base, ...overrides]) {\n merged.set(item[key], item)\n }\n return [...merged.values()]\n}\n\n// Builds a desktop `.jbrowse` snapshot (a config plus a defaultSession) from a\n// web root config and its live session. Session-scoped tracks/assemblies are\n// hoisted into the config arrays so they open as first-class config entries in\n// desktop; the rest of the session becomes the defaultSession. Web sessions are\n// always UriLocation-based, so the result is fully portable to desktop.\nexport function toDesktopSnapshot(\n jbrowse: Record<string, unknown>,\n session: Record<string, unknown>,\n): Record<string, unknown> {\n const { sessionTracks, sessionAssemblies, ...defaultSession } = session\n return {\n ...jbrowse,\n tracks: mergeById(\n asObjArray(jbrowse.tracks),\n asObjArray(sessionTracks),\n 'trackId',\n ),\n assemblies: mergeById(\n asObjArray(jbrowse.assemblies),\n asObjArray(sessionAssemblies),\n 'name',\n ),\n defaultSession,\n }\n}\n", "{\n \"version\": \"1.0.16\",\n \"license\": \"MIT\",\n \"name\": \"@cmdcolin/jbrowse-plugin-hubs\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/GMOD/jbrowse-plugin-hubs.git\"\n },\n \"keywords\": [\n \"jbrowse\",\n \"jbrowse2\"\n ],\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.esm.js\",\n \"typings\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"start\": \"node esbuild.mjs --watch\",\n \"test\": \"echo 'no tests'\",\n \"format\": \"pnpm prettier --write .\",\n \"prebuild\": \"pnpm clean\",\n \"build\": \"tsc && NODE_ENV=production node esbuild.mjs\",\n \"lint\": \"eslint src --report-unused-disable-directives --max-warnings 0\",\n \"prepack\": \"pnpm build\",\n \"postversion\": \"git push --follow-tags\",\n \"preversion\": \"pnpm lint && pnpm build && pnpm host-compat\",\n \"host-compat\": \"node scripts/host-compat-probe.mjs --bundle dist/jbrowse-plugin-hubs.umd.production.min.js\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@fal-works/esbuild-plugin-global-externals\": \"^2.1.2\",\n \"@jbrowse/core\": \"^4.3.0\",\n \"@jbrowse/mobx-state-tree\": \"^5.11.1\",\n \"@mui/material\": \"^7.3.11\",\n \"@mui/system\": \"^7.3.11\",\n \"@mui/x-data-grid\": \"^8.29.1\",\n \"@types/node\": \"^25.9.3\",\n \"@types/react\": \"^19.2.17\",\n \"@typescript-eslint/eslint-plugin\": \"^8.61.1\",\n \"@typescript-eslint/parser\": \"^8.61.1\",\n \"esbuild\": \"^0.27.7\",\n \"eslint\": \"^10.5.0\",\n \"eslint-plugin-import-x\": \"^4.16.2\",\n \"eslint-plugin-react\": \"^7.37.5\",\n \"eslint-plugin-react-hooks\": \"^7.1.1\",\n \"eslint-plugin-unicorn\": \"^64.0.0\",\n \"mobx\": \"^6.16.1\",\n \"mobx-react\": \"^9.2.2\",\n \"prettier\": \"^3.8.4\",\n \"pretty-bytes\": \"^7.1.0\",\n \"puppeteer-core\": \"^25.4.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"rimraf\": \"^6.1.3\",\n \"rxjs\": \"^7.8.2\",\n \"serve\": \"^14.2.6\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.61.1\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n"],
5
+ "mappings": "gpBAAA,IAAAA,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,sBAAsB,ICAtD,IAAAC,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,oBAAoB,ICApD,IAAAC,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,iBAAiB,ICAjD,IAAAC,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,IAAAC,EAAmB,OACnBC,EAAkD,OAClDC,EAAqC,OCFrC,SAASC,EAAmBC,EAAmB,CAC7C,GAAM,CAACC,EAAMC,CAAI,EAAIF,EAAU,MAAM,GAAG,EACxC,GAAI,CAACE,EACH,OAEF,IAAMC,EAAQD,EAAK,MAAM,SAAS,EAClC,GAAI,CAACC,GAASA,EAAM,OAAS,EAC3B,OAEF,GAAM,CAACC,EAAIC,EAAIC,CAAE,EAAIH,EACrB,MAAO,mCAAmCF,CAAI,IAAIG,CAAE,IAAIC,CAAE,IAAIC,CAAE,IAAIN,CAAS,cAC/E,CAsBO,SAASO,EAAcC,EAAsB,CAClD,GAAI,CAAC,WAAW,KAAKA,CAAY,EAC/B,MAAO,CAAC,EAEV,GAAIA,EAAa,WAAW,MAAM,GAAKA,EAAa,WAAW,MAAM,EAAG,CACtE,IAAMC,EAAMV,EAAmBS,CAAY,EAC3C,OAAOC,EAAM,CAACA,CAAG,EAAI,CAAC,CACxB,CACA,IAAMR,EAAO,4BAA4BO,CAAY,GACrD,MAAO,CAAC,GAAGP,CAAI,gBAAiB,GAAGA,CAAI,cAAc,CACvD,CCxBA,IAAMS,EAAsB,CAC1B,aACA,oBACA,WACA,UACA,UACA,eACA,aACF,EAKA,SAASC,EAAeC,EAAUC,EAAiB,CACjD,GAAI,MAAM,QAAQD,CAAK,EACrB,OAAOA,EAAM,IAAIE,GAAQH,EAAYG,EAAMD,CAAI,CAAC,EAElD,GAAI,OAAOD,GAAU,UAAYA,IAAU,KAAM,CAC/C,IAAMG,EAAU,OAAO,QAAQH,CAAK,EAAE,IAAI,CAAC,CAACI,EAAKF,CAAI,IAAM,CACzDE,EACAL,EAAYG,EAAMD,CAAI,CACxB,CAAC,EACD,OAAO,OAAO,YACZ,OAAQD,EAA4B,KAAQ,UAC1C,EAAE,YAAaA,GACb,CAAC,GAAGG,EAAS,CAAC,UAAWF,CAAI,CAAC,EAC9BE,CACN,CACF,CACA,OAAOH,CACT,CAEO,SAASK,EACdC,EACAC,EACAC,EACyB,CACzB,IAAMC,EAAWH,EAAO,YAAY,KAClCI,GAAKA,EAAE,OAASH,GAAgBG,EAAE,SAAS,SAASH,CAAY,CAClE,EACA,GAAI,CAACE,EACH,OAEF,IAAME,EAAS,IAAI,IAAIL,EAAO,QAAQ,IAAIM,GAAS,CAACA,EAAM,QAASA,CAAK,CAAC,CAAC,EACpEC,EAAcf,EAAoB,IACtCgB,GAAUH,EAAO,IAAI,GAAGF,EAAS,IAAI,IAAIK,CAAM,EAAE,GAAG,OACtD,EAAE,KAAKC,GAAWA,IAAY,MAAS,EACvC,MAAO,CACL,SAAUhB,EAAYU,EAAUD,CAAS,EACzC,YAAaK,GAAed,EAAYc,EAAaL,CAAS,CAChE,CACF,CAIA,eAAeQ,EAAiBT,EAAsB,CACpD,QAAWU,KAAOC,EAAcX,CAAY,EAAG,CAC7C,IAAMY,EAAW,MAAM,MAAMF,CAAG,EAChC,GAAIE,EAAS,GAAI,CACf,IAAMb,EAAU,MAAMa,EAAS,KAAK,EACpC,OAAOd,EAAmBC,EAAQC,EAAcU,CAAG,CACrD,CACA,GAAIE,EAAS,SAAW,IACtB,MAAM,IAAI,MAAM,GAAGF,CAAG,UAAUE,EAAS,MAAM,EAAE,CAErD,CAEF,CAEA,IAAMC,EAAe,IAAI,IAEzB,SAASC,EAASd,EAAsB,CACtC,IAAIe,EAAcF,EAAa,IAAIb,CAAY,EAC/C,OAAKe,IACHA,EAAcN,EAAiBT,CAAY,EAAE,MAAOgB,GAAmB,CACrEH,EAAa,OAAOb,CAAY,EAChC,QAAQ,MAAMgB,CAAK,CAErB,CAAC,EACDH,EAAa,IAAIb,EAAce,CAAW,GAErCA,CACT,CAQA,eAAsBE,EACpBC,EACAC,EACA,CACA,IAAMC,EACJ,OAAOF,GAAc,UAAYA,IAAc,KAAOA,EAAY,CAAC,EAC/DG,EAAQ,MAAM,QAAQF,CAAa,EACrCA,EAAc,OAAQG,GAAyB,OAAOA,GAAS,QAAQ,EACvE,CAAC,EACCC,EAAU,MAAM,QAAQ,IAC5BF,EACG,OACCC,GAAQ,CAAC,OAAO,OAAOF,EAAQE,CAAI,GAAKX,EAAcW,CAAI,EAAE,OAAS,CACvE,EACC,IAAI,MAAMA,GAAQ,CAACA,EAAM,MAAMR,EAASQ,CAAI,CAAC,CAAU,CAC5D,EACA,MAAO,CACL,GAAGF,EACH,GAAG,OAAO,YACRG,EAAQ,OAAO,CAAC,CAAC,CAAER,CAAW,IAAMA,IAAgB,MAAS,CAC/D,CACF,CACF,CC/HO,SAASS,EAAOC,EAAYC,EAAkB,CACnD,IAAMC,EAAM,IAAI,gBAAgBF,CAAI,EAC9BG,EAAS,SAAS,cAAc,GAAG,EACzCA,EAAO,KAAOD,EACdC,EAAO,SAAWF,EAClBE,EAAO,IAAM,WACbA,EAAO,MAAM,EACb,WAAW,IAAM,CACf,IAAI,gBAAgBD,CAAG,CACzB,EAAG,GAAK,CACV,CAEA,SAASE,EAAWC,EAA2C,CAC7D,OAAO,MAAM,QAAQA,CAAK,EAAKA,EAAsC,CAAC,CACxE,CAKA,SAASC,EACPC,EACAC,EACAC,EAC2B,CAC3B,IAAMC,EAAS,IAAI,IACnB,QAAWC,IAAQ,CAAC,GAAGJ,EAAM,GAAGC,CAAS,EACvCE,EAAO,IAAIC,EAAKF,CAAG,EAAGE,CAAI,EAE5B,MAAO,CAAC,GAAGD,EAAO,OAAO,CAAC,CAC5B,CAOO,SAASE,EACdC,EACAC,EACyB,CACzB,GAAM,CAAE,cAAAC,EAAe,kBAAAC,EAAmB,GAAGC,CAAe,EAAIH,EAChE,MAAO,CACL,GAAGD,EACH,OAAQP,EACNF,EAAWS,EAAQ,MAAM,EACzBT,EAAWW,CAAa,EACxB,SACF,EACA,WAAYT,EACVF,EAAWS,EAAQ,UAAU,EAC7BT,EAAWY,CAAiB,EAC5B,MACF,EACA,eAAAC,CACF,CACF,CC1DE,IAAAC,EAAW,SJqBb,SAASC,EAAuBC,EAA+B,CAC7D,GAAM,CAAE,QAAAC,CAAQ,KAAI,WAAwCD,CAAO,EAC7DE,EAAOC,KACX,eAAqCF,CAAO,KAC5C,eAAqCD,CAAO,CAC9C,EACAI,EACE,IAAI,KAAK,CAAC,KAAK,UAAUF,EAAM,KAAM,CAAC,CAAC,EAAG,CAAE,KAAM,kBAAmB,CAAC,EACtE,iBACF,CACF,CA2BA,IAAMG,EAAY,IAAI,QAEtB,SAASC,EAAcN,EAAkBO,EAAsB,CAC7D,IAAIC,EAAQH,EAAU,IAAIL,CAAO,EAKjC,OAJKQ,IACHA,EAAQ,IAAI,IACZH,EAAU,IAAIL,EAASQ,CAAK,GAE1BA,EAAM,IAAID,CAAY,EACjB,IAETC,EAAM,IAAID,CAAY,EACf,GACT,CAMA,eAAeE,EACbT,EACAO,EACAG,EACA,CACA,IAAMC,EAAe,UAAUJ,CAAY,GAC3C,GACE,EAAAP,EAAQ,YAAY,KAAKY,GAAKA,EAAE,eAAiBD,CAAY,GAC7D,CAACL,EAAcN,EAASO,CAAY,IAItC,QAAWM,KAAOH,EAEhB,IADiB,MAAM,MAAMG,EAAK,CAAE,OAAQ,MAAO,CAAC,GACvC,GAAI,CACf,IAAMC,EAAO,CACX,KAAM,wBACN,IAAAD,EACA,KAAM,QAAQN,CAAY,GAC1B,cAAe,CAACA,CAAY,EAC5B,aAAAI,CACF,EACAX,EAAQ,kBAAkBc,CAAI,EAa9Bd,EAAQ,eAAec,EAAM,CAAE,OAAQ,EAAK,CAAC,EAC7C,MACF,EAEJ,CAEA,IAAqBC,EAArB,cAA8C,EAAAC,OAAO,CACnD,KAAO,mBACP,QAAUC,EAEV,QAAQC,EAA8B,CACpCA,EAAc,oBACZ,kCACA,CAACC,EAAeC,IAAS,CACvB,IAAMpB,EAAUoB,EAAK,QACfb,EAAea,EAAK,aACpBV,EAAOH,EAAec,EAAcd,CAAY,EAAI,CAAC,EAC3D,OAAIP,GAAWO,GAAgBG,EAAK,OAAS,EAkBpCD,EAAsBT,EAASO,EAAcG,CAAI,EAAE,MACvDY,GAAe,CACd,QAAQ,MAAMA,CAAC,CACjB,CACF,EAIKH,CACT,CACF,EAIAD,EAAc,oBACZ,0BACA,CAACK,EAAoBH,IACnBI,EAAmBD,EAAWH,EAAK,aAAa,CACpD,CACF,CAEA,UAAUF,EAA8B,CAItC,GAAI,CAAC,iBAAc,yBAAsBA,EAAc,SAAS,EAY9D,GAAI,CACFA,EAAc,UAAU,aAAa,QAAS,CAC5C,MAAO,sCACP,QAAUlB,GAAkC,CAC1CD,EAAuBC,CAAO,CAChC,CACF,CAAC,CACH,OAASsB,EAAG,CACV,QAAQ,MAAM,kDAAmDA,CAAC,CACpE,CAEJ,CACF",
6
+ "names": ["require_Plugin", "__commonJSMin", "exports", "module", "require_util", "__commonJSMin", "exports", "module", "require_mobx_state_tree", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "import_Plugin", "import_util", "import_mobx_state_tree", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrls", "assemblyName", "url", "GENE_TRACK_SUFFIXES", "withBaseUri", "value", "base", "item", "entries", "key", "describeFromConfig", "config", "assemblyName", "configUrl", "assembly", "a", "tracks", "track", "geneAdapter", "suffix", "adapter", "fetchDescription", "url", "getConfigUrls", "response", "descriptions", "describe", "description", "error", "describeAssemblies", "described", "assemblyNames", "before", "names", "name", "answers", "saveAs", "blob", "filename", "url", "anchor", "asObjArray", "value", "mergeById", "base", "overrides", "key", "merged", "item", "toDesktopSnapshot", "jbrowse", "session", "sessionTracks", "sessionAssemblies", "defaultSession", "version", "downloadDesktopSession", "session", "jbrowse", "snap", "toDesktopSnapshot", "saveAs", "attempted", "markAttempted", "assemblyName", "names", "connectIfConfigExists", "uris", "connectionId", "f", "uri", "conf", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "defaultResult", "args", "getConfigUrls", "e", "described", "describeAssemblies"]
7
7
  }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.14",
2
+ "version": "1.0.16",
3
3
  "license": "MIT",
4
4
  "name": "@cmdcolin/jbrowse-plugin-hubs",
5
5
  "repository": {
@@ -0,0 +1,44 @@
1
+ function getGenArkConfigUrl(accession: string) {
2
+ const [base, rest] = accession.split('_')
3
+ if (!rest) {
4
+ return undefined
5
+ }
6
+ const match = rest.match(/.{1,3}/g)
7
+ if (!match || match.length < 3) {
8
+ return undefined
9
+ }
10
+ const [b1, b2, b3] = match
11
+ return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`
12
+ }
13
+
14
+ // Candidate urls for an assembly name, most preferred first. The connection
15
+ // probe and the describe lookup each take the first that exists, so a name with
16
+ // no hosted config anywhere is still answered by staying quiet.
17
+ //
18
+ // A UCSC db gets `minimal.json` ahead of `config.json`. Both carry the same
19
+ // `assemblies` block (minimal.json is a filtered copy of config.json, tracks
20
+ // dropped to NCBI RefSeq / GENCODE / RepeatMasker / gaps), and the assembly is
21
+ // the whole reason this connection exists — the tracks are a bonus for the
22
+ // panel it opens. The full config is an expensive way to learn a sequence
23
+ // adapter: hg38 is 2.1MB against minimal's 300KB, hg19 1.35MB against 57KB,
24
+ // hs1 603KB against 12KB, and the parse is followed by MST instantiating every
25
+ // track config in it on the main thread (hg38 595 against 33, hs1 624 against
26
+ // 7). hg38 alone names 239 distinct mate assemblies across its 239 synteny
27
+ // tracks, so a session that opens a few of them pays this several times over.
28
+ //
29
+ // What minimal.json costs is the mate genome's long tail — conservation,
30
+ // expression, the rest of the hub. Those are still one "Add connection" away
31
+ // on the full config url, and nothing about this panel implied they were
32
+ // coming. GenArk hubs have no minimal.json and don't need one; their whole
33
+ // config is ~40KB.
34
+ export function getConfigUrls(assemblyName: string) {
35
+ if (!/^[\w.]+$/.test(assemblyName)) {
36
+ return []
37
+ }
38
+ if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
39
+ const url = getGenArkConfigUrl(assemblyName)
40
+ return url ? [url] : []
41
+ }
42
+ const base = `https://jbrowse.org/ucsc/${assemblyName}`
43
+ return [`${base}/minimal.json`, `${base}/config.json`]
44
+ }
@@ -0,0 +1,132 @@
1
+ import { getConfigUrls } from './configUrls'
2
+
3
+ interface Description {
4
+ assembly: HubAssembly
5
+ geneAdapter?: Record<string, unknown>
6
+ }
7
+
8
+ interface HubAssembly {
9
+ name: string
10
+ aliases?: string[]
11
+ }
12
+
13
+ interface HubConfig {
14
+ assemblies?: HubAssembly[]
15
+ tracks?: { trackId: string; adapter?: Record<string, unknown> }[]
16
+ }
17
+
18
+ // jb2hubs' defaultGeneTrackId order, which also picks the track minimal.json
19
+ // keeps and a hub genome's default session opens
20
+ const GENE_TRACK_SUFFIXES = [
21
+ 'ncbiRefSeq',
22
+ 'ncbiRefSeqCurated',
23
+ 'ncbiGene',
24
+ 'refGene',
25
+ 'ensGene',
26
+ 'augustusGene',
27
+ 'xenoRefGene',
28
+ ]
29
+
30
+ // A TwoBit adapter's bare `chromSizes` resolves against the `baseUri` beside
31
+ // its `uri`, so rewriting each `uri` alone sent jb2hubs' relative chrom.sizes
32
+ // to the page's origin
33
+ function withBaseUri<T>(value: T, base: string): T {
34
+ if (Array.isArray(value)) {
35
+ return value.map(item => withBaseUri(item, base)) as T
36
+ }
37
+ if (typeof value === 'object' && value !== null) {
38
+ const entries = Object.entries(value).map(([key, item]) => [
39
+ key,
40
+ withBaseUri(item, base),
41
+ ])
42
+ return Object.fromEntries(
43
+ typeof (value as { uri?: unknown }).uri === 'string' &&
44
+ !('baseUri' in value)
45
+ ? [...entries, ['baseUri', base]]
46
+ : entries,
47
+ ) as T
48
+ }
49
+ return value
50
+ }
51
+
52
+ export function describeFromConfig(
53
+ config: HubConfig,
54
+ assemblyName: string,
55
+ configUrl: string,
56
+ ): Description | undefined {
57
+ const assembly = config.assemblies?.find(
58
+ a => a.name === assemblyName || a.aliases?.includes(assemblyName),
59
+ )
60
+ if (!assembly) {
61
+ return undefined
62
+ }
63
+ const tracks = new Map(config.tracks?.map(track => [track.trackId, track]))
64
+ const geneAdapter = GENE_TRACK_SUFFIXES.map(
65
+ suffix => tracks.get(`${assembly.name}-${suffix}`)?.adapter,
66
+ ).find(adapter => adapter !== undefined)
67
+ return {
68
+ assembly: withBaseUri(assembly, configUrl),
69
+ geneAdapter: geneAdapter && withBaseUri(geneAdapter, configUrl),
70
+ }
71
+ }
72
+
73
+ // throws on anything but a found config or a 404, so only a definitive
74
+ // answer is cached
75
+ async function fetchDescription(assemblyName: string) {
76
+ for (const url of getConfigUrls(assemblyName)) {
77
+ const response = await fetch(url)
78
+ if (response.ok) {
79
+ const config = (await response.json()) as HubConfig
80
+ return describeFromConfig(config, assemblyName, url)
81
+ }
82
+ if (response.status !== 404) {
83
+ throw new Error(`${url}: HTTP ${response.status}`)
84
+ }
85
+ }
86
+ return undefined
87
+ }
88
+
89
+ const descriptions = new Map<string, Promise<Description | undefined>>()
90
+
91
+ function describe(assemblyName: string) {
92
+ let description = descriptions.get(assemblyName)
93
+ if (!description) {
94
+ description = fetchDescription(assemblyName).catch((error: unknown) => {
95
+ descriptions.delete(assemblyName)
96
+ console.error(error)
97
+ return undefined
98
+ })
99
+ descriptions.set(assemblyName, description)
100
+ }
101
+ return description
102
+ }
103
+
104
+ /**
105
+ * Core-describeAssemblies: the assembly config and gene track adapter the
106
+ * hosted config of each named genome holds, read without connecting it. A
107
+ * name another plugin already described, or one no hosted config could hold,
108
+ * is left alone
109
+ */
110
+ export async function describeAssemblies(
111
+ described: unknown,
112
+ assemblyNames: unknown,
113
+ ) {
114
+ const before =
115
+ typeof described === 'object' && described !== null ? described : {}
116
+ const names = Array.isArray(assemblyNames)
117
+ ? assemblyNames.filter((name): name is string => typeof name === 'string')
118
+ : []
119
+ const answers = await Promise.all(
120
+ names
121
+ .filter(
122
+ name => !Object.hasOwn(before, name) && getConfigUrls(name).length > 0,
123
+ )
124
+ .map(async name => [name, await describe(name)] as const),
125
+ )
126
+ return {
127
+ ...before,
128
+ ...Object.fromEntries(
129
+ answers.filter(([, description]) => description !== undefined),
130
+ ),
131
+ }
132
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,8 @@ import Plugin from '@jbrowse/core/Plugin'
2
2
  import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'
3
3
  import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'
4
4
 
5
+ import { getConfigUrls } from './configUrls'
6
+ import { describeAssemblies } from './describeAssemblies'
5
7
  import { saveAs, toDesktopSnapshot } from './util'
6
8
  import { version } from '../package.json'
7
9
 
@@ -30,48 +32,6 @@ function downloadDesktopSession(session: AbstractSessionModel) {
30
32
  )
31
33
  }
32
34
 
33
- function getGenArkConfigUrl(accession: string) {
34
- const [base, rest] = accession.split('_')
35
- if (!rest) {
36
- return undefined
37
- }
38
- const match = rest.match(/.{1,3}/g)
39
- if (!match || match.length < 3) {
40
- return undefined
41
- }
42
- const [b1, b2, b3] = match
43
- return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`
44
- }
45
-
46
- // Candidate urls for an assembly name, most preferred first. The probe below
47
- // takes the first that exists, so a name with no hosted config anywhere is
48
- // still answered by staying quiet.
49
- //
50
- // A UCSC db gets `minimal.json` ahead of `config.json`. Both carry the same
51
- // `assemblies` block (minimal.json is a filtered copy of config.json, tracks
52
- // dropped to NCBI RefSeq / GENCODE / RepeatMasker / gaps), and the assembly is
53
- // the whole reason this connection exists — the tracks are a bonus for the
54
- // panel it opens. The full config is an expensive way to learn a sequence
55
- // adapter: hg38 is 2.1MB against minimal's 300KB, hg19 1.35MB against 57KB,
56
- // hs1 603KB against 12KB, and the parse is followed by MST instantiating every
57
- // track config in it on the main thread (hg38 595 against 33, hs1 624 against
58
- // 7). hg38 alone names 239 distinct mate assemblies across its 239 synteny
59
- // tracks, so a session that opens a few of them pays this several times over.
60
- //
61
- // What minimal.json costs is the mate genome's long tail — conservation,
62
- // expression, the rest of the hub. Those are still one "Add connection" away
63
- // on the full config url, and nothing about this panel implied they were
64
- // coming. GenArk hubs have no minimal.json and don't need one; their whole
65
- // config is ~40KB.
66
- function getConfigUrls(assemblyName: string) {
67
- if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
68
- const url = getGenArkConfigUrl(assemblyName)
69
- return url ? [url] : []
70
- }
71
- const base = `https://jbrowse.org/ucsc/${assemblyName}`
72
- return [`${base}/minimal.json`, `${base}/config.json`]
73
- }
74
-
75
35
  // Every assembly name this plugin has already taken a run at, whether the probe
76
36
  // is still in flight or came back with nothing. The extension point fires on an
77
37
  // unresolved read, and `get` is read from render paths, so a name that resolves
@@ -197,6 +157,14 @@ export default class HubsViewerPlugin extends Plugin {
197
157
  return defaultResult
198
158
  },
199
159
  )
160
+ // A multi-way synteny lane whose genome the session lacks asks here for
161
+ // its assembly config and gene track. Cores older than the point never
162
+ // fire it, so this is inert on them
163
+ pluginManager.addToExtensionPoint(
164
+ 'Core-describeAssemblies',
165
+ (described: unknown, args) =>
166
+ describeAssemblies(described, args.assemblyNames),
167
+ )
200
168
  }
201
169
 
202
170
  configure(pluginManager: PluginManager) {