@cmdcolin/jbrowse-plugin-hubs 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -23,45 +23,107 @@ function getGenArkConfigUrl(accession) {
|
|
|
23
23
|
const [b1, b2, b3] = match;
|
|
24
24
|
return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`;
|
|
25
25
|
}
|
|
26
|
-
|
|
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) {
|
|
27
47
|
if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
|
|
28
|
-
|
|
48
|
+
const url = getGenArkConfigUrl(assemblyName);
|
|
49
|
+
return url ? [url] : [];
|
|
29
50
|
}
|
|
30
|
-
|
|
51
|
+
const base = `https://jbrowse.org/ucsc/${assemblyName}`;
|
|
52
|
+
return [`${base}/minimal.json`, `${base}/config.json`];
|
|
31
53
|
}
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
54
|
+
// Every assembly name this plugin has already taken a run at, whether the probe
|
|
55
|
+
// is still in flight or came back with nothing. The extension point fires on an
|
|
56
|
+
// unresolved read, and `get` is read from render paths, so a name that resolves
|
|
57
|
+
// to no hosted config is asked about again and again — GenArk strain hubs
|
|
58
|
+
// (mouseStrains and friends) live under /hubs/genark/<hub>/<strain>/ and are not
|
|
59
|
+
// at the url guessed from the name, and a session can name an assembly this host
|
|
60
|
+
// has never heard of at all. Clearing this once the probe settled (what an
|
|
61
|
+
// earlier `finally` did) meant the next read re-probed, i.e. one HEAD per render
|
|
62
|
+
// forever for exactly the names nothing can supply.
|
|
63
|
+
//
|
|
64
|
+
// A name that DID resolve is also in here, and is separately deduped by
|
|
65
|
+
// connectionId once the connection exists. Nothing is ever removed within a
|
|
66
|
+
// session: a probe answers a question about a url that does not change within a
|
|
67
|
+
// page load.
|
|
68
|
+
//
|
|
69
|
+
// Keyed by session, because the connection a probe created belongs to the
|
|
70
|
+
// session and goes with it. A flat Set would remember a name across
|
|
71
|
+
// `setSession` and leave the new session unable to resolve a genome the old one
|
|
72
|
+
// had. WeakMap so a replaced session is still collectable.
|
|
73
|
+
//
|
|
74
|
+
// jbrowse-core@main reports each unknown name to the extension point once per
|
|
75
|
+
// session, which makes this redundant there — but this plugin runs against
|
|
76
|
+
// whatever core the host page shipped, down to the v4.0.0 floor, where it is
|
|
77
|
+
// the only thing standing between a mistyped assembly name and an unbounded
|
|
78
|
+
// request stream.
|
|
79
|
+
const attempted = new WeakMap();
|
|
80
|
+
function markAttempted(session, assemblyName) {
|
|
81
|
+
let names = attempted.get(session);
|
|
82
|
+
if (!names) {
|
|
83
|
+
names = new Set();
|
|
84
|
+
attempted.set(session, names);
|
|
85
|
+
}
|
|
86
|
+
if (names.has(assemblyName)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
names.add(assemblyName);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
// Connect to the first of `uris` that exists. Connecting to a config that 404s
|
|
93
|
+
// puts a red error snackbar over a session that is otherwise working, so probe
|
|
94
|
+
// first and stay quiet when there's nothing there — some other handler, or the
|
|
95
|
+
// session itself, may be supplying the assembly.
|
|
96
|
+
async function connectIfConfigExists(session, assemblyName, uris) {
|
|
45
97
|
const connectionId = `jb2hub-${assemblyName}`;
|
|
46
|
-
if (
|
|
47
|
-
!session
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
98
|
+
if (session.connections.find(f => f.connectionId === connectionId) ||
|
|
99
|
+
!markAttempted(session, assemblyName)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
for (const uri of uris) {
|
|
103
|
+
const response = await fetch(uri, { method: 'HEAD' });
|
|
104
|
+
if (response.ok) {
|
|
105
|
+
const conf = {
|
|
106
|
+
type: 'JB2TrackHubConnection',
|
|
107
|
+
uri,
|
|
108
|
+
name: `conn_${assemblyName}`,
|
|
109
|
+
assemblyNames: [assemblyName],
|
|
110
|
+
connectionId,
|
|
111
|
+
};
|
|
112
|
+
session.addConnectionConf(conf);
|
|
113
|
+
// `silent`: no "Successfully loaded" snackbar. Nobody asked for this
|
|
114
|
+
// connection — it exists because a track referenced an assembly the
|
|
115
|
+
// session didn't have — so announcing it is noise over whatever the
|
|
116
|
+
// user was doing, and it lands on top of any screenshot of a hub
|
|
117
|
+
// genome. The property is newer than every released core (it landed
|
|
118
|
+
// after v4.3.0) and this plugin runs against whatever core the host
|
|
119
|
+
// page shipped, down to the v4.0.0 floor, but passing it is safe
|
|
120
|
+
// regardless: MST drops an undeclared key off a model snapshot rather
|
|
121
|
+
// than rejecting it (verified against the fork — the instance is
|
|
122
|
+
// created, the key is neither readable on it nor in its snapshot), so
|
|
123
|
+
// an older core makes the same connection it always did and keeps its
|
|
124
|
+
// toast.
|
|
125
|
+
session.makeConnection(conf, { silent: true });
|
|
126
|
+
return;
|
|
65
127
|
}
|
|
66
128
|
}
|
|
67
129
|
}
|
|
@@ -72,12 +134,12 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
72
134
|
pluginManager.addToExtensionPoint('Core-handleUnrecognizedAssembly', (defaultResult, args) => {
|
|
73
135
|
const session = args.session;
|
|
74
136
|
const assemblyName = args.assemblyName;
|
|
75
|
-
const
|
|
76
|
-
if (session && assemblyName &&
|
|
137
|
+
const uris = assemblyName ? getConfigUrls(assemblyName) : [];
|
|
138
|
+
if (session && assemblyName && uris.length > 0) {
|
|
77
139
|
// the extension point is sync; the probe and the connection it may
|
|
78
140
|
// create land later, which is fine because assemblyManager re-reads
|
|
79
141
|
// reactively once the assembly shows up
|
|
80
|
-
connectIfConfigExists(session, assemblyName,
|
|
142
|
+
connectIfConfigExists(session, assemblyName, uris).catch((e) => {
|
|
81
143
|
console.error(e);
|
|
82
144
|
});
|
|
83
145
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE/D,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAYzC,2EAA2E;AAC3E,+EAA+E;AAC/E,+EAA+E;AAC/E,SAAS,sBAAsB,CAAC,OAA6B;IAC3D,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAiC,OAAO,CAAC,CAAA;IACpE,MAAM,IAAI,GAAG,iBAAiB,CAC5B,WAAW,CAA0B,OAAO,CAAC,EAC7C,WAAW,CAA0B,OAAO,CAAC,CAC9C,CAAA;IACD,MAAM,CACJ,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,EACvE,iBAAiB,CAClB,CAAA;AACH,CAAC;AAED,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,SAAS,
|
|
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
|
|
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:()=>u});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 p(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(p(n.tracks),p(t),"trackId"),assemblies:x(p(n.assemblies),p(o),"name"),defaultSession:s}}var A="1.0.13";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 u=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);})();
|
|
2
2
|
//# sourceMappingURL=jbrowse-plugin-hubs.umd.production.min.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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) => 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\nfunction getConfigUrl(assemblyName: string) {\n if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {\n return getGenArkConfigUrl(assemblyName)\n }\n return `https://jbrowse.org/ucsc/${assemblyName}/config.json`\n}\n\n// Names already being resolved, so the extension point firing repeatedly for\n// the same assembly (it fires on every unresolved read) starts one probe, not\n// one per read. The connection itself is deduped by connectionId, but that\n// check only happens once the probe has come back.\nconst pending = new Set<string>()\n\n// The url is a guess from the name, and plenty of assemblies aren't at it:\n// GenArk strain hubs (mouseStrains and friends) live under\n// /hubs/genark/<hub>/<strain>/, and a session can name an assembly this host\n// has never heard of. Connecting to a config that 404s puts a red error\n// snackbar over a session that is otherwise working, so probe first and stay\n// quiet when there's nothing there \u2014 some other handler, or the session\n// itself, may be supplying the assembly.\nasync function connectIfConfigExists(\n session: Session,\n assemblyName: string,\n uri: string,\n) {\n const connectionId = `jb2hub-${assemblyName}`\n if (\n !pending.has(assemblyName) &&\n !session.connections.find(f => f.connectionId === connectionId)\n ) {\n pending.add(assemblyName)\n try {\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 session.makeConnection(conf)\n }\n } finally {\n pending.delete(assemblyName)\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 uri = session && assemblyName && getConfigUrl(assemblyName)\n if (session && assemblyName && uri) {\n // the extension point is sync; the probe and the connection it may\n // create land later, which is fine because assemblyManager re-reads\n // reactively once the assembly shows up\n connectIfConfigExists(session, assemblyName, uri).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.12\",\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\"\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 \"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,OCE9B,SAASC,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,SFmBb,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,CAEA,SAASG,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,
|
|
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", "
|
|
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 extension point is sync; the probe and the connection it may\n // create land later, which is fine because assemblyManager re-reads\n // reactively once the assembly shows up\n 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.13\",\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"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.13",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@cmdcolin/jbrowse-plugin-hubs",
|
|
5
5
|
"repository": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"lint": "eslint src --report-unused-disable-directives --max-warnings 0",
|
|
28
28
|
"prepack": "pnpm build",
|
|
29
29
|
"postversion": "git push --follow-tags",
|
|
30
|
-
"preversion": "pnpm lint"
|
|
30
|
+
"preversion": "pnpm lint && pnpm build && pnpm host-compat",
|
|
31
|
+
"host-compat": "node scripts/host-compat-probe.mjs --bundle dist/jbrowse-plugin-hubs.umd.production.min.js"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@eslint/js": "^10.0.1",
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
"mobx-react": "^9.2.2",
|
|
52
53
|
"prettier": "^3.8.4",
|
|
53
54
|
"pretty-bytes": "^7.1.0",
|
|
55
|
+
"puppeteer-core": "^25.4.0",
|
|
54
56
|
"react": "^19.2.7",
|
|
55
57
|
"react-dom": "^19.2.7",
|
|
56
58
|
"rimraf": "^6.1.3",
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { AbstractSessionModel } from '@jbrowse/core/util'
|
|
|
10
10
|
import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree'
|
|
11
11
|
|
|
12
12
|
interface Session {
|
|
13
|
-
makeConnection: (conf: unknown) => void
|
|
13
|
+
makeConnection: (conf: unknown, initialSnapshot?: unknown) => void
|
|
14
14
|
addConnectionConf: (conf: unknown) => void
|
|
15
15
|
connections: { connectionId: string }[]
|
|
16
16
|
}
|
|
@@ -43,52 +43,116 @@ function getGenArkConfigUrl(accession: string) {
|
|
|
43
43
|
return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
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) {
|
|
47
67
|
if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
|
|
48
|
-
|
|
68
|
+
const url = getGenArkConfigUrl(assemblyName)
|
|
69
|
+
return url ? [url] : []
|
|
49
70
|
}
|
|
50
|
-
|
|
71
|
+
const base = `https://jbrowse.org/ucsc/${assemblyName}`
|
|
72
|
+
return [`${base}/minimal.json`, `${base}/config.json`]
|
|
51
73
|
}
|
|
52
74
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
75
|
+
// Every assembly name this plugin has already taken a run at, whether the probe
|
|
76
|
+
// is still in flight or came back with nothing. The extension point fires on an
|
|
77
|
+
// unresolved read, and `get` is read from render paths, so a name that resolves
|
|
78
|
+
// to no hosted config is asked about again and again — GenArk strain hubs
|
|
79
|
+
// (mouseStrains and friends) live under /hubs/genark/<hub>/<strain>/ and are not
|
|
80
|
+
// at the url guessed from the name, and a session can name an assembly this host
|
|
81
|
+
// has never heard of at all. Clearing this once the probe settled (what an
|
|
82
|
+
// earlier `finally` did) meant the next read re-probed, i.e. one HEAD per render
|
|
83
|
+
// forever for exactly the names nothing can supply.
|
|
84
|
+
//
|
|
85
|
+
// A name that DID resolve is also in here, and is separately deduped by
|
|
86
|
+
// connectionId once the connection exists. Nothing is ever removed within a
|
|
87
|
+
// session: a probe answers a question about a url that does not change within a
|
|
88
|
+
// page load.
|
|
89
|
+
//
|
|
90
|
+
// Keyed by session, because the connection a probe created belongs to the
|
|
91
|
+
// session and goes with it. A flat Set would remember a name across
|
|
92
|
+
// `setSession` and leave the new session unable to resolve a genome the old one
|
|
93
|
+
// had. WeakMap so a replaced session is still collectable.
|
|
94
|
+
//
|
|
95
|
+
// jbrowse-core@main reports each unknown name to the extension point once per
|
|
96
|
+
// session, which makes this redundant there — but this plugin runs against
|
|
97
|
+
// whatever core the host page shipped, down to the v4.0.0 floor, where it is
|
|
98
|
+
// the only thing standing between a mistyped assembly name and an unbounded
|
|
99
|
+
// request stream.
|
|
100
|
+
const attempted = new WeakMap<Session, Set<string>>()
|
|
58
101
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
function markAttempted(session: Session, assemblyName: string) {
|
|
103
|
+
let names = attempted.get(session)
|
|
104
|
+
if (!names) {
|
|
105
|
+
names = new Set()
|
|
106
|
+
attempted.set(session, names)
|
|
107
|
+
}
|
|
108
|
+
if (names.has(assemblyName)) {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
names.add(assemblyName)
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Connect to the first of `uris` that exists. Connecting to a config that 404s
|
|
116
|
+
// puts a red error snackbar over a session that is otherwise working, so probe
|
|
117
|
+
// first and stay quiet when there's nothing there — some other handler, or the
|
|
118
|
+
// session itself, may be supplying the assembly.
|
|
66
119
|
async function connectIfConfigExists(
|
|
67
120
|
session: Session,
|
|
68
121
|
assemblyName: string,
|
|
69
|
-
|
|
122
|
+
uris: string[],
|
|
70
123
|
) {
|
|
71
124
|
const connectionId = `jb2hub-${assemblyName}`
|
|
72
125
|
if (
|
|
73
|
-
|
|
74
|
-
!session
|
|
126
|
+
session.connections.find(f => f.connectionId === connectionId) ||
|
|
127
|
+
!markAttempted(session, assemblyName)
|
|
75
128
|
) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
session.addConnectionConf(conf)
|
|
88
|
-
session.makeConnection(conf)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
for (const uri of uris) {
|
|
132
|
+
const response = await fetch(uri, { method: 'HEAD' })
|
|
133
|
+
if (response.ok) {
|
|
134
|
+
const conf = {
|
|
135
|
+
type: 'JB2TrackHubConnection',
|
|
136
|
+
uri,
|
|
137
|
+
name: `conn_${assemblyName}`,
|
|
138
|
+
assemblyNames: [assemblyName],
|
|
139
|
+
connectionId,
|
|
89
140
|
}
|
|
90
|
-
|
|
91
|
-
|
|
141
|
+
session.addConnectionConf(conf)
|
|
142
|
+
// `silent`: no "Successfully loaded" snackbar. Nobody asked for this
|
|
143
|
+
// connection — it exists because a track referenced an assembly the
|
|
144
|
+
// session didn't have — so announcing it is noise over whatever the
|
|
145
|
+
// user was doing, and it lands on top of any screenshot of a hub
|
|
146
|
+
// genome. The property is newer than every released core (it landed
|
|
147
|
+
// after v4.3.0) and this plugin runs against whatever core the host
|
|
148
|
+
// page shipped, down to the v4.0.0 floor, but passing it is safe
|
|
149
|
+
// regardless: MST drops an undeclared key off a model snapshot rather
|
|
150
|
+
// than rejecting it (verified against the fork — the instance is
|
|
151
|
+
// created, the key is neither readable on it nor in its snapshot), so
|
|
152
|
+
// an older core makes the same connection it always did and keeps its
|
|
153
|
+
// toast.
|
|
154
|
+
session.makeConnection(conf, { silent: true })
|
|
155
|
+
return
|
|
92
156
|
}
|
|
93
157
|
}
|
|
94
158
|
}
|
|
@@ -103,12 +167,12 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
103
167
|
(defaultResult, args) => {
|
|
104
168
|
const session = args.session as Session | undefined
|
|
105
169
|
const assemblyName = args.assemblyName as string | undefined
|
|
106
|
-
const
|
|
107
|
-
if (session && assemblyName &&
|
|
170
|
+
const uris = assemblyName ? getConfigUrls(assemblyName) : []
|
|
171
|
+
if (session && assemblyName && uris.length > 0) {
|
|
108
172
|
// the extension point is sync; the probe and the connection it may
|
|
109
173
|
// create land later, which is fine because assemblyManager re-reads
|
|
110
174
|
// reactively once the assembly shows up
|
|
111
|
-
connectIfConfigExists(session, assemblyName,
|
|
175
|
+
connectIfConfigExists(session, assemblyName, uris).catch(
|
|
112
176
|
(e: unknown) => {
|
|
113
177
|
console.error(e)
|
|
114
178
|
},
|