@cmdcolin/jbrowse-plugin-hubs 1.0.4 → 1.0.6

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.d.ts CHANGED
@@ -6,4 +6,3 @@ export default class HubsViewerPlugin extends Plugin {
6
6
  install(pluginManager: PluginManager): void;
7
7
  configure(_pluginManager: PluginManager): void;
8
8
  }
9
- export { type HubSession, fetchAssemblyHub, fetchingAssemblies, } from './fetchAssemblyHub.ts';
package/dist/index.js CHANGED
@@ -1,6 +1,23 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
- import { fetchAssemblyHub, fetchingAssemblies } from "./fetchAssemblyHub.js";
3
2
  import { version } from '../package.json';
3
+ function getGenArkConfigUrl(accession) {
4
+ const [base, rest] = accession.split('_');
5
+ if (!rest) {
6
+ return undefined;
7
+ }
8
+ const match = rest.match(/.{1,3}/g);
9
+ if (!match || match.length < 3) {
10
+ return undefined;
11
+ }
12
+ const [b1, b2, b3] = match;
13
+ return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`;
14
+ }
15
+ function getConfigUrl(assemblyName) {
16
+ if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
17
+ return getGenArkConfigUrl(assemblyName);
18
+ }
19
+ return `https://jbrowse.org/ucsc/${assemblyName}/config.json`;
20
+ }
4
21
  export default class HubsViewerPlugin extends Plugin {
5
22
  name = 'HubsViewerPlugin';
6
23
  version = version;
@@ -8,21 +25,27 @@ export default class HubsViewerPlugin extends Plugin {
8
25
  pluginManager.addToExtensionPoint('Core-handleUnrecognizedAssembly', (_defaultResult, args) => {
9
26
  const session = args.session;
10
27
  const assemblyName = args.assemblyName;
11
- // Skip if missing required args
12
28
  if (!session || !assemblyName) {
13
29
  return;
14
30
  }
15
- // Skip if already fetching (must check this first to prevent recursion)
16
- if (fetchingAssemblies.has(assemblyName)) {
31
+ const uri = getConfigUrl(assemblyName);
32
+ if (!uri) {
17
33
  return;
18
34
  }
19
- // Mark as fetching before any other checks that might re-trigger this
20
- fetchingAssemblies.add(assemblyName);
21
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
22
- fetchAssemblyHub(session, assemblyName);
35
+ const connectionId = `jb2hub-${assemblyName}`;
36
+ if (!session.connections.find(f => f.connectionId === connectionId)) {
37
+ const conf = {
38
+ type: 'JB2TrackHubConnection',
39
+ uri,
40
+ name: `conn_${assemblyName}`,
41
+ assemblyNames: [assemblyName],
42
+ connectionId,
43
+ };
44
+ session.addConnectionConf(conf);
45
+ session.makeConnection(conf);
46
+ }
23
47
  });
24
48
  }
25
49
  configure(_pluginManager) { }
26
50
  }
27
- export { fetchAssemblyHub, fetchingAssemblies, } from "./fetchAssemblyHub.js";
28
51
  //# sourceMappingURL=index.js.map
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;AAGzC,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAIzC,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,MAAM;IAClD,IAAI,GAAG,kBAAkB,CAAA;IACzB,OAAO,GAAG,OAAO,CAAA;IAEjB,OAAO,CAAC,aAA4B;QAClC,aAAa,CAAC,mBAAmB,CAC/B,iCAAiC,EACjC,CAAC,cAAc,EAAE,IAAI,EAAE,EAAE;YACvB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAiC,CAAA;YACtD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAkC,CAAA;YAE5D,gCAAgC;YAChC,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;gBAC9B,OAAM;YACR,CAAC;YAED,wEAAwE;YACxE,IAAI,kBAAkB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;gBACzC,OAAM;YACR,CAAC;YAED,sEAAsE;YACtE,kBAAkB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YAEpC,mEAAmE;YACnE,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;QACzC,CAAC,CACF,CAAA;IACH,CAAC;IAED,SAAS,CAAC,cAA6B,IAAG,CAAC;CAC5C;AAED,OAAO,EAEL,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,sBAAsB,CAAA;AAGzC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAQzC,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,YAAY,CAAC,YAAoB;IACxC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,OAAO,kBAAkB,CAAC,YAAY,CAAC,CAAA;IACzC,CAAC;IACD,OAAO,4BAA4B,YAAY,cAAc,CAAA;AAC/D,CAAC;AAED,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,MAAM;IAClD,IAAI,GAAG,kBAAkB,CAAA;IACzB,OAAO,GAAG,OAAO,CAAA;IAEjB,OAAO,CAAC,aAA4B;QAClC,aAAa,CAAC,mBAAmB,CAC/B,iCAAiC,EACjC,CAAC,cAAc,EAAE,IAAI,EAAE,EAAE;YACvB,MAAM,OAAO,GAAG,IAAI,CAAC,OAA8B,CAAA;YACnD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAkC,CAAA;YAC5D,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;gBAC9B,OAAM;YACR,CAAC;YACD,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;YACtC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAM;YACR,CAAC;YACD,MAAM,YAAY,GAAG,UAAU,YAAY,EAAE,CAAA;YAC7C,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,EAAE,CAAC;gBACpE,MAAM,IAAI,GAAG;oBACX,IAAI,EAAE,uBAAuB;oBAC7B,GAAG;oBACH,IAAI,EAAE,QAAQ,YAAY,EAAE;oBAC5B,aAAa,EAAE,CAAC,YAAY,CAAC;oBAC7B,YAAY;iBACb,CAAA;gBACD,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAA;gBAC/B,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;YAC9B,CAAC;QACH,CAAC,CACF,CAAA;IACH,CAAC;IAED,SAAS,CAAC,cAA6B,IAAG,CAAC;CAC5C"}
@@ -1,2 +1,2 @@
1
- "use strict";var JBrowsePluginHubs=(()=>{var g=Object.create;var a=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var h=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var j=(e,s)=>()=>(s||e((s={exports:{}}).exports,s),s.exports),k=(e,s)=>{for(var n in s)a(e,n,{get:s[n],enumerable:!0})},u=(e,s,n,t)=>{if(s&&typeof s=="object"||typeof s=="function")for(let r of w(s))!x.call(e,r)&&r!==n&&a(e,r,{get:()=>s[r],enumerable:!(t=y(s,r))||t.enumerable});return e};var A=(e,s,n)=>(n=e!=null?g(h(e)):{},u(s||!e||!e.__esModule?a(n,"default",{value:e,enumerable:!0}):n,e)),v=e=>u(a({},"__esModule",{value:!0}),e);var b=j((M,d)=>{d.exports=JBrowseExports["@jbrowse/core/Plugin"]});var S={};k(S,{default:()=>c,fetchAssemblyHub:()=>l,fetchingAssemblies:()=>o});var f=A(b());var o=new Set;function H(e){let[s,n]=e.split("_");if(!n)return;let t=n.match(/.{1,3}/g);if(!t||t.length<3)return;let[r,i,p]=t;return`https://jbrowse.org/hubs/genark/${s}/${r}/${i}/${p}/${e}/config.json`}function $(e){return e.startsWith("GCA_")||e.startsWith("GCF_")?H(e):`https://jbrowse.org/ucsc/${e}/config.json`}async function l(e,s){let n=$(s);if(n)try{let t=await fetch(n);if(!t.ok)throw new Error(`HTTP ${t.status} from ${n}`);let r=await t.json();for(let i of r.assemblies??[])e.assemblyManager.get(i.name)||e.addSessionAssembly(i);for(let i of r.tracks??[])e.addTrackConf(i)}catch(t){console.error(t),e.notifyError(`Failed to load assembly ${s}: ${t}`,t)}finally{o.delete(s)}}var m="1.0.4";var c=class extends f.default{name="HubsViewerPlugin";version=m;install(s){s.addToExtensionPoint("Core-handleUnrecognizedAssembly",(n,t)=>{let r=t.session,i=t.assemblyName;!r||!i||o.has(i)||(o.add(i),l(r,i))})}configure(s){}};return v(S);})();
1
+ "use strict";var JBrowsePluginHubs=(()=>{var b=Object.create;var a=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var h=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var y=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),C=(n,e)=>{for(var t in e)a(n,t,{get:e[t],enumerable:!0})},l=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of w(e))!x.call(n,s)&&s!==t&&a(n,s,{get:()=>e[s],enumerable:!(i=j(e,s))||i.enumerable});return n};var k=(n,e,t)=>(t=n!=null?b(h(n)):{},l(e||!n||!n.__esModule?a(t,"default",{value:n,enumerable:!0}):t,n)),v=n=>l(a({},"__esModule",{value:!0}),n);var p=y((I,g)=>{g.exports=JBrowseExports["@jbrowse/core/Plugin"]});var M={};C(M,{default:()=>d});var m=k(p());var f="1.0.6";function _(n){let[e,t]=n.split("_");if(!t)return;let i=t.match(/.{1,3}/g);if(!i||i.length<3)return;let[s,o,r]=i;return`https://jbrowse.org/hubs/genark/${e}/${s}/${o}/${r}/${n}/config.json`}function P(n){return n.startsWith("GCA_")||n.startsWith("GCF_")?_(n):`https://jbrowse.org/ucsc/${n}/config.json`}var d=class extends m.default{name="HubsViewerPlugin";version=f;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(t,i)=>{let s=i.session,o=i.assemblyName;if(!s||!o)return;let r=P(o);if(!r)return;let u=`jb2hub-${o}`;if(!s.connections.find(c=>c.connectionId===u)){let c={type:"JB2TrackHubConnection",uri:r,name:`conn_${o}`,assemblyNames:[o],connectionId:u};s.addConnectionConf(c),s.makeConnection(c)}})}configure(e){}};return v(M);})();
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", "../src/index.ts", "../src/fetchAssemblyHub.ts", "../package.json"],
4
- "sourcesContent": ["module.exports = JBrowseExports[\"@jbrowse/core/Plugin\"];", "import Plugin from '@jbrowse/core/Plugin'\nimport PluginManager from '@jbrowse/core/PluginManager'\n\nimport { fetchAssemblyHub, fetchingAssemblies } from './fetchAssemblyHub.ts'\nimport { version } from '../package.json'\n\nimport type { HubSession } from './fetchAssemblyHub.ts'\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 HubSession | undefined\n const assemblyName = args.assemblyName as string | undefined\n\n // Skip if missing required args\n if (!session || !assemblyName) {\n return\n }\n\n // Skip if already fetching (must check this first to prevent recursion)\n if (fetchingAssemblies.has(assemblyName)) {\n return\n }\n\n // Mark as fetching before any other checks that might re-trigger this\n fetchingAssemblies.add(assemblyName)\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n fetchAssemblyHub(session, assemblyName)\n },\n )\n }\n\n configure(_pluginManager: PluginManager) {}\n}\n\nexport {\n type HubSession,\n fetchAssemblyHub,\n fetchingAssemblies,\n} from './fetchAssemblyHub.ts'\n", "// Track assemblies we're currently fetching to avoid duplicate requests\nexport const fetchingAssemblies = new Set<string>()\n\nexport interface HubSession {\n assemblyManager: { get: (name: string) => unknown }\n addSessionAssembly: (assembly: unknown) => void\n addTrackConf: (track: unknown) => void\n notifyError: (msg: string, e: unknown) => void\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 // GenArk assemblies (GCA_* or GCF_*)\n if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {\n return getGenArkConfigUrl(assemblyName)\n }\n // UCSC assemblies (hg38, mm10, etc.)\n return `https://jbrowse.org/ucsc/${assemblyName}/config.json`\n}\n\nexport async function fetchAssemblyHub(\n session: HubSession,\n assemblyName: string,\n) {\n const url = getConfigUrl(assemblyName)\n if (!url) {\n return\n }\n try {\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} from ${url}`)\n }\n const config = (await response.json()) as {\n assemblies?: { name: string }[]\n tracks?: unknown[]\n }\n\n // Add assemblies directly to session\n for (const assembly of config.assemblies ?? []) {\n if (!session.assemblyManager.get(assembly.name)) {\n session.addSessionAssembly(assembly)\n }\n }\n\n // Add tracks directly to session (addTrackConf handles duplicates)\n for (const track of config.tracks ?? []) {\n session.addTrackConf(track)\n }\n } catch (e) {\n console.error(e)\n session.notifyError(`Failed to load assembly ${assemblyName}: ${e}`, e)\n } finally {\n fetchingAssemblies.delete(assemblyName)\n }\n}\n", "{\n \"version\": \"1.0.4\",\n \"license\": \"MIT\",\n \"name\": \"@cmdcolin/jbrowse-plugin-hubs\",\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\": \"node --experimental-strip-types --test src/fetchAssemblyHub.test.ts\",\n \"format\": \"prettier --write .\",\n \"prebuild\": \"npm run clean\",\n \"build\": \"tsc && NODE_ENV=production node esbuild.mjs\",\n \"lint\": \"eslint --report-unused-disable-directives --max-warnings 0\",\n \"prepack\": \"npm run build\",\n \"postversion\": \"git push --follow-tags\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"^7.28.6\",\n \"@babel/preset-react\": \"^7.10.4\",\n \"@emotion/react\": \"^11.10.4\",\n \"@emotion/styled\": \"^11.14.1\",\n \"@eslint/js\": \"^9.39.2\",\n \"@fal-works/esbuild-plugin-global-externals\": \"^2.1.2\",\n \"@jbrowse/core\": \"^4.1.3\",\n \"@jbrowse/mobx-state-tree\": \"^5.0.2\",\n \"@jbrowse/plugin-data-management\": \"^4.1.3\",\n \"@jbrowse/plugin-linear-genome-view\": \"^4.1.3\",\n \"@mui/material\": \"^7.3.7\",\n \"@mui/system\": \"^7.3.7\",\n \"@mui/x-data-grid\": \"^8.26.0\",\n \"@types/d3-array\": \"^3.2.1\",\n \"@types/d3-hierarchy\": \"^3.1.7\",\n \"@types/node\": \"^25.1.0\",\n \"@types/react\": \"^19.2.10\",\n \"esbuild\": \"^0.27.2\",\n \"eslint\": \"^9.39.2\",\n \"eslint-plugin-import\": \"^2.31.0\",\n \"eslint-plugin-unicorn\": \"^62.0.0\",\n \"mobx\": \"^6.0.0\",\n \"mobx-react\": \"^9.0.1\",\n \"prettier\": \"^3.8.1\",\n \"pretty-bytes\": \"^7.0.0\",\n \"react\": \"^19.2.4\",\n \"react-dom\": \"^19.2.4\",\n \"rimraf\": \"^6.1.2\",\n \"rxjs\": \"^7.8.1\",\n \"serve\": \"^14.2.0\",\n \"typescript\": \"^5.1.6\",\n \"typescript-eslint\": \"^8.54.0\"\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,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,qBAAAC,EAAA,uBAAAC,IAAA,IAAAC,EAAmB,OCCZ,IAAMC,EAAqB,IAAI,IAStC,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,CAEA,SAASO,EAAaC,EAAsB,CAE1C,OAAIA,EAAa,WAAW,MAAM,GAAKA,EAAa,WAAW,MAAM,EAC5DT,EAAmBS,CAAY,EAGjC,4BAA4BA,CAAY,cACjD,CAEA,eAAsBC,EACpBC,EACAF,EACA,CACA,IAAMG,EAAMJ,EAAaC,CAAY,EACrC,GAAKG,EAGL,GAAI,CACF,IAAMC,EAAW,MAAM,MAAMD,CAAG,EAChC,GAAI,CAACC,EAAS,GACZ,MAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,SAASD,CAAG,EAAE,EAEvD,IAAME,EAAU,MAAMD,EAAS,KAAK,EAMpC,QAAWE,KAAYD,EAAO,YAAc,CAAC,EACtCH,EAAQ,gBAAgB,IAAII,EAAS,IAAI,GAC5CJ,EAAQ,mBAAmBI,CAAQ,EAKvC,QAAWC,KAASF,EAAO,QAAU,CAAC,EACpCH,EAAQ,aAAaK,CAAK,CAE9B,OAASC,EAAG,CACV,QAAQ,MAAMA,CAAC,EACfN,EAAQ,YAAY,2BAA2BF,CAAY,KAAKQ,CAAC,GAAIA,CAAC,CACxE,QAAE,CACAlB,EAAmB,OAAOU,CAAY,CACxC,CACF,CClEE,IAAAS,EAAW,QFOb,IAAqBC,EAArB,cAA8C,EAAAC,OAAO,CACnD,KAAO,mBACP,QAAUC,EAEV,QAAQC,EAA8B,CACpCA,EAAc,oBACZ,kCACA,CAACC,EAAgBC,IAAS,CACxB,IAAMC,EAAUD,EAAK,QACfE,EAAeF,EAAK,aAGtB,CAACC,GAAW,CAACC,GAKbC,EAAmB,IAAID,CAAY,IAKvCC,EAAmB,IAAID,CAAY,EAGnCE,EAAiBH,EAASC,CAAY,EACxC,CACF,CACF,CAEA,UAAUG,EAA+B,CAAC,CAC5C",
6
- "names": ["require_Plugin", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "fetchAssemblyHub", "fetchingAssemblies", "import_Plugin", "fetchingAssemblies", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrl", "assemblyName", "fetchAssemblyHub", "session", "url", "response", "config", "assembly", "track", "e", "version", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "_defaultResult", "args", "session", "assemblyName", "fetchingAssemblies", "fetchAssemblyHub", "_pluginManager"]
3
+ "sources": ["global-externals:@jbrowse/core/Plugin", "../src/index.ts", "../package.json"],
4
+ "sourcesContent": ["module.exports = JBrowseExports[\"@jbrowse/core/Plugin\"];", "import Plugin from '@jbrowse/core/Plugin'\nimport PluginManager from '@jbrowse/core/PluginManager'\n\nimport { version } from '../package.json'\n\ninterface Session {\n makeConnection: (conf: unknown) => void\n addConnectionConf: (conf: unknown) => void\n connections: { connectionId: string }[]\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\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 if (!session || !assemblyName) {\n return\n }\n const uri = getConfigUrl(assemblyName)\n if (!uri) {\n return\n }\n const connectionId = `jb2hub-${assemblyName}`\n if (!session.connections.find(f => f.connectionId === connectionId)) {\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 },\n )\n }\n\n configure(_pluginManager: PluginManager) {}\n}\n", "{\n \"version\": \"1.0.6\",\n \"license\": \"MIT\",\n \"name\": \"@cmdcolin/jbrowse-plugin-hubs\",\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\": \"prettier --write .\",\n \"prebuild\": \"yarn clean\",\n \"build\": \"tsc && NODE_ENV=production node esbuild.mjs\",\n \"lint\": \"eslint --report-unused-disable-directives --max-warnings 0\",\n \"prepack\": \"yarn build\",\n \"postversion\": \"git push --follow-tags\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@fal-works/esbuild-plugin-global-externals\": \"^2.1.2\",\n \"@jbrowse/core\": \"^4.1.3\",\n \"@jbrowse/mobx-state-tree\": \"^5.0.2\",\n \"@mui/material\": \"^7.3.7\",\n \"@mui/system\": \"^7.3.7\",\n \"@mui/x-data-grid\": \"^8.26.0\",\n \"@types/node\": \"^25.1.0\",\n \"@types/react\": \"^19.2.10\",\n \"esbuild\": \"^0.27.2\",\n \"eslint\": \"^9.39.2\",\n \"eslint-plugin-import\": \"^2.31.0\",\n \"eslint-plugin-unicorn\": \"^62.0.0\",\n \"mobx\": \"^6.0.0\",\n \"mobx-react\": \"^9.0.1\",\n \"prettier\": \"^3.8.1\",\n \"pretty-bytes\": \"^7.0.0\",\n \"react\": \"^19.2.4\",\n \"react-dom\": \"^19.2.4\",\n \"rimraf\": \"^6.1.2\",\n \"rxjs\": \"^7.8.1\",\n \"serve\": \"^14.2.0\",\n \"typescript\": \"^5.1.6\",\n \"typescript-eslint\": \"^8.54.0\"\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,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,IAAAC,EAAmB,OCCjB,IAAAC,EAAW,QDUb,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,CAEA,SAASO,EAAaC,EAAsB,CAC1C,OAAIA,EAAa,WAAW,MAAM,GAAKA,EAAa,WAAW,MAAM,EAC5DT,EAAmBS,CAAY,EAEjC,4BAA4BA,CAAY,cACjD,CAEA,IAAqBC,EAArB,cAA8C,EAAAC,OAAO,CACnD,KAAO,mBACP,QAAUC,EAEV,QAAQC,EAA8B,CACpCA,EAAc,oBACZ,kCACA,CAACC,EAAgBC,IAAS,CACxB,IAAMC,EAAUD,EAAK,QACfN,EAAeM,EAAK,aAC1B,GAAI,CAACC,GAAW,CAACP,EACf,OAEF,IAAMQ,EAAMT,EAAaC,CAAY,EACrC,GAAI,CAACQ,EACH,OAEF,IAAMC,EAAe,UAAUT,CAAY,GAC3C,GAAI,CAACO,EAAQ,YAAY,KAAKG,GAAKA,EAAE,eAAiBD,CAAY,EAAG,CACnE,IAAME,EAAO,CACX,KAAM,wBACN,IAAAH,EACA,KAAM,QAAQR,CAAY,GAC1B,cAAe,CAACA,CAAY,EAC5B,aAAAS,CACF,EACAF,EAAQ,kBAAkBI,CAAI,EAC9BJ,EAAQ,eAAeI,CAAI,CAC7B,CACF,CACF,CACF,CAEA,UAAUC,EAA+B,CAAC,CAC5C",
6
+ "names": ["require_Plugin", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "import_Plugin", "version", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrl", "assemblyName", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "_defaultResult", "args", "session", "uri", "connectionId", "f", "conf", "_pluginManager"]
7
7
  }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.4",
2
+ "version": "1.0.6",
3
3
  "license": "MIT",
4
4
  "name": "@cmdcolin/jbrowse-plugin-hubs",
5
5
  "keywords": [
@@ -16,30 +16,22 @@
16
16
  "scripts": {
17
17
  "clean": "rimraf dist",
18
18
  "start": "node esbuild.mjs --watch",
19
- "test": "node --experimental-strip-types --test src/fetchAssemblyHub.test.ts",
19
+ "test": "echo 'no tests'",
20
20
  "format": "prettier --write .",
21
- "prebuild": "npm run clean",
21
+ "prebuild": "yarn clean",
22
22
  "build": "tsc && NODE_ENV=production node esbuild.mjs",
23
23
  "lint": "eslint --report-unused-disable-directives --max-warnings 0",
24
- "prepack": "npm run build",
24
+ "prepack": "yarn build",
25
25
  "postversion": "git push --follow-tags"
26
26
  },
27
27
  "devDependencies": {
28
- "@babel/core": "^7.28.6",
29
- "@babel/preset-react": "^7.10.4",
30
- "@emotion/react": "^11.10.4",
31
- "@emotion/styled": "^11.14.1",
32
28
  "@eslint/js": "^9.39.2",
33
29
  "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
34
30
  "@jbrowse/core": "^4.1.3",
35
31
  "@jbrowse/mobx-state-tree": "^5.0.2",
36
- "@jbrowse/plugin-data-management": "^4.1.3",
37
- "@jbrowse/plugin-linear-genome-view": "^4.1.3",
38
32
  "@mui/material": "^7.3.7",
39
33
  "@mui/system": "^7.3.7",
40
34
  "@mui/x-data-grid": "^8.26.0",
41
- "@types/d3-array": "^3.2.1",
42
- "@types/d3-hierarchy": "^3.1.7",
43
35
  "@types/node": "^25.1.0",
44
36
  "@types/react": "^19.2.10",
45
37
  "esbuild": "^0.27.2",
package/src/index.ts CHANGED
@@ -1,10 +1,33 @@
1
1
  import Plugin from '@jbrowse/core/Plugin'
2
2
  import PluginManager from '@jbrowse/core/PluginManager'
3
3
 
4
- import { fetchAssemblyHub, fetchingAssemblies } from './fetchAssemblyHub.ts'
5
4
  import { version } from '../package.json'
6
5
 
7
- import type { HubSession } from './fetchAssemblyHub.ts'
6
+ interface Session {
7
+ makeConnection: (conf: unknown) => void
8
+ addConnectionConf: (conf: unknown) => void
9
+ connections: { connectionId: string }[]
10
+ }
11
+
12
+ function getGenArkConfigUrl(accession: string) {
13
+ const [base, rest] = accession.split('_')
14
+ if (!rest) {
15
+ return undefined
16
+ }
17
+ const match = rest.match(/.{1,3}/g)
18
+ if (!match || match.length < 3) {
19
+ return undefined
20
+ }
21
+ const [b1, b2, b3] = match
22
+ return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`
23
+ }
24
+
25
+ function getConfigUrl(assemblyName: string) {
26
+ if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
27
+ return getGenArkConfigUrl(assemblyName)
28
+ }
29
+ return `https://jbrowse.org/ucsc/${assemblyName}/config.json`
30
+ }
8
31
 
9
32
  export default class HubsViewerPlugin extends Plugin {
10
33
  name = 'HubsViewerPlugin'
@@ -14,33 +37,30 @@ export default class HubsViewerPlugin extends Plugin {
14
37
  pluginManager.addToExtensionPoint(
15
38
  'Core-handleUnrecognizedAssembly',
16
39
  (_defaultResult, args) => {
17
- const session = args.session as HubSession | undefined
40
+ const session = args.session as Session | undefined
18
41
  const assemblyName = args.assemblyName as string | undefined
19
-
20
- // Skip if missing required args
21
42
  if (!session || !assemblyName) {
22
43
  return
23
44
  }
24
-
25
- // Skip if already fetching (must check this first to prevent recursion)
26
- if (fetchingAssemblies.has(assemblyName)) {
45
+ const uri = getConfigUrl(assemblyName)
46
+ if (!uri) {
27
47
  return
28
48
  }
29
-
30
- // Mark as fetching before any other checks that might re-trigger this
31
- fetchingAssemblies.add(assemblyName)
32
-
33
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
34
- fetchAssemblyHub(session, assemblyName)
49
+ const connectionId = `jb2hub-${assemblyName}`
50
+ if (!session.connections.find(f => f.connectionId === connectionId)) {
51
+ const conf = {
52
+ type: 'JB2TrackHubConnection',
53
+ uri,
54
+ name: `conn_${assemblyName}`,
55
+ assemblyNames: [assemblyName],
56
+ connectionId,
57
+ }
58
+ session.addConnectionConf(conf)
59
+ session.makeConnection(conf)
60
+ }
35
61
  },
36
62
  )
37
63
  }
38
64
 
39
65
  configure(_pluginManager: PluginManager) {}
40
66
  }
41
-
42
- export {
43
- type HubSession,
44
- fetchAssemblyHub,
45
- fetchingAssemblies,
46
- } from './fetchAssemblyHub.ts'
@@ -1,10 +0,0 @@
1
- export declare const fetchingAssemblies: Set<string>;
2
- export interface HubSession {
3
- assemblyManager: {
4
- get: (name: string) => unknown;
5
- };
6
- addSessionAssembly: (assembly: unknown) => void;
7
- addTrackConf: (track: unknown) => void;
8
- notifyError: (msg: string, e: unknown) => void;
9
- }
10
- export declare function fetchAssemblyHub(session: HubSession, assemblyName: string): Promise<void>;
@@ -1,53 +0,0 @@
1
- // Track assemblies we're currently fetching to avoid duplicate requests
2
- export const fetchingAssemblies = new Set();
3
- function getGenArkConfigUrl(accession) {
4
- const [base, rest] = accession.split('_');
5
- if (!rest) {
6
- return undefined;
7
- }
8
- const match = rest.match(/.{1,3}/g);
9
- if (!match || match.length < 3) {
10
- return undefined;
11
- }
12
- const [b1, b2, b3] = match;
13
- return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`;
14
- }
15
- function getConfigUrl(assemblyName) {
16
- // GenArk assemblies (GCA_* or GCF_*)
17
- if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
18
- return getGenArkConfigUrl(assemblyName);
19
- }
20
- // UCSC assemblies (hg38, mm10, etc.)
21
- return `https://jbrowse.org/ucsc/${assemblyName}/config.json`;
22
- }
23
- export async function fetchAssemblyHub(session, assemblyName) {
24
- const url = getConfigUrl(assemblyName);
25
- if (!url) {
26
- return;
27
- }
28
- try {
29
- const response = await fetch(url);
30
- if (!response.ok) {
31
- throw new Error(`HTTP ${response.status} from ${url}`);
32
- }
33
- const config = (await response.json());
34
- // Add assemblies directly to session
35
- for (const assembly of config.assemblies ?? []) {
36
- if (!session.assemblyManager.get(assembly.name)) {
37
- session.addSessionAssembly(assembly);
38
- }
39
- }
40
- // Add tracks directly to session (addTrackConf handles duplicates)
41
- for (const track of config.tracks ?? []) {
42
- session.addTrackConf(track);
43
- }
44
- }
45
- catch (e) {
46
- console.error(e);
47
- session.notifyError(`Failed to load assembly ${assemblyName}: ${e}`, e);
48
- }
49
- finally {
50
- fetchingAssemblies.delete(assemblyName);
51
- }
52
- }
53
- //# sourceMappingURL=fetchAssemblyHub.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetchAssemblyHub.js","sourceRoot":"","sources":["../src/fetchAssemblyHub.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAU,CAAA;AASnD,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,YAAY,CAAC,YAAoB;IACxC,qCAAqC;IACrC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,OAAO,kBAAkB,CAAC,YAAY,CAAC,CAAA;IACzC,CAAC;IACD,qCAAqC;IACrC,OAAO,4BAA4B,YAAY,cAAc,CAAA;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAmB,EACnB,YAAoB;IAEpB,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;IACtC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAM;IACR,CAAC;IACD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAA;QACxD,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAGpC,CAAA;QAED,qCAAqC;QACrC,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChD,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;YACtC,CAAC;QACH,CAAC;QAED,mEAAmE;QACnE,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;YACxC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAChB,OAAO,CAAC,WAAW,CAAC,2BAA2B,YAAY,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IACzE,CAAC;YAAS,CAAC;QACT,kBAAkB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACzC,CAAC;AACH,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,203 +0,0 @@
1
- import assert from 'node:assert';
2
- import { beforeEach, describe, it } from 'node:test';
3
- import { fetchAssemblyHub, fetchingAssemblies } from "./fetchAssemblyHub.js";
4
- function createMockSession() {
5
- const calls = {
6
- addSessionAssembly: [],
7
- addTrackConf: [],
8
- notifyError: [],
9
- };
10
- return {
11
- session: {
12
- assemblyManager: {
13
- get: (_name) => undefined,
14
- },
15
- addSessionAssembly: (assembly) => {
16
- calls.addSessionAssembly.push(assembly);
17
- },
18
- addTrackConf: (track) => {
19
- calls.addTrackConf.push(track);
20
- },
21
- notifyError: (msg, e) => {
22
- calls.notifyError.push({ msg, e });
23
- },
24
- },
25
- calls,
26
- };
27
- }
28
- function mockFetch(config, ok = true, status = 200) {
29
- const originalFetch = globalThis.fetch;
30
- globalThis.fetch = (async () => ({
31
- ok,
32
- status,
33
- json: async () => config,
34
- }));
35
- return () => {
36
- globalThis.fetch = originalFetch;
37
- };
38
- }
39
- describe('fetchAssemblyHub', () => {
40
- beforeEach(() => {
41
- fetchingAssemblies.clear();
42
- });
43
- it('adds assemblies from fetched config', async () => {
44
- const { session, calls } = createMockSession();
45
- const mockConfig = {
46
- assemblies: [{ name: 'hg38', sequence: {} }],
47
- tracks: [],
48
- };
49
- const restore = mockFetch(mockConfig);
50
- fetchingAssemblies.add('hg38');
51
- await fetchAssemblyHub(session, 'hg38');
52
- restore();
53
- assert.strictEqual(calls.addSessionAssembly.length, 1);
54
- assert.deepStrictEqual(calls.addSessionAssembly[0], {
55
- name: 'hg38',
56
- sequence: {},
57
- });
58
- assert.strictEqual(fetchingAssemblies.has('hg38'), false);
59
- });
60
- it('adds tracks from fetched config', async () => {
61
- const { session, calls } = createMockSession();
62
- const mockConfig = {
63
- assemblies: [],
64
- tracks: [{ trackId: 'genes', type: 'FeatureTrack' }],
65
- };
66
- const restore = mockFetch(mockConfig);
67
- fetchingAssemblies.add('hg38');
68
- await fetchAssemblyHub(session, 'hg38');
69
- restore();
70
- assert.strictEqual(calls.addTrackConf.length, 1);
71
- assert.deepStrictEqual(calls.addTrackConf[0], {
72
- trackId: 'genes',
73
- type: 'FeatureTrack',
74
- });
75
- });
76
- it('skips assembly if already exists', async () => {
77
- const { session, calls } = createMockSession();
78
- session.assemblyManager.get = () => ({ name: 'hg38' });
79
- const mockConfig = {
80
- assemblies: [{ name: 'hg38', sequence: {} }],
81
- tracks: [],
82
- };
83
- const restore = mockFetch(mockConfig);
84
- fetchingAssemblies.add('hg38');
85
- await fetchAssemblyHub(session, 'hg38');
86
- restore();
87
- assert.strictEqual(calls.addSessionAssembly.length, 0);
88
- });
89
- it('handles fetch error gracefully', async () => {
90
- const { session, calls } = createMockSession();
91
- const restore = mockFetch({}, false, 404);
92
- fetchingAssemblies.add('nonexistent');
93
- await fetchAssemblyHub(session, 'nonexistent');
94
- restore();
95
- assert.strictEqual(calls.notifyError.length, 1);
96
- assert.strictEqual(fetchingAssemblies.has('nonexistent'), false);
97
- });
98
- it('handles network error gracefully', async () => {
99
- const { session, calls } = createMockSession();
100
- const originalFetch = globalThis.fetch;
101
- globalThis.fetch = (async () => {
102
- throw new Error('Network error');
103
- });
104
- fetchingAssemblies.add('hg38');
105
- await fetchAssemblyHub(session, 'hg38');
106
- globalThis.fetch = originalFetch;
107
- assert.strictEqual(calls.notifyError.length, 1);
108
- assert.strictEqual(fetchingAssemblies.has('hg38'), false);
109
- });
110
- it('cleans up fetchingAssemblies on success', async () => {
111
- const { session } = createMockSession();
112
- const mockConfig = { assemblies: [], tracks: [] };
113
- const restore = mockFetch(mockConfig);
114
- fetchingAssemblies.add('hg38');
115
- assert.strictEqual(fetchingAssemblies.has('hg38'), true);
116
- await fetchAssemblyHub(session, 'hg38');
117
- restore();
118
- assert.strictEqual(fetchingAssemblies.has('hg38'), false);
119
- });
120
- });
121
- describe('extension point handler', () => {
122
- it('fetchingAssemblies prevents duplicate requests', () => {
123
- fetchingAssemblies.add('hg38');
124
- const shouldFetch = !fetchingAssemblies.has('hg38');
125
- assert.strictEqual(shouldFetch, false);
126
- });
127
- it('allows fetch for new assembly', () => {
128
- fetchingAssemblies.clear();
129
- const shouldFetch = !fetchingAssemblies.has('hg38');
130
- assert.strictEqual(shouldFetch, true);
131
- });
132
- });
133
- describe('GenArk assembly support', () => {
134
- beforeEach(() => {
135
- fetchingAssemblies.clear();
136
- });
137
- it('fetches GenArk assembly with GCF_ prefix', async () => {
138
- const { session, calls } = createMockSession();
139
- const mockConfig = {
140
- assemblies: [{ name: 'GCF_009858895.2', sequence: {} }],
141
- tracks: [],
142
- };
143
- let fetchedUrl = '';
144
- const originalFetch = globalThis.fetch;
145
- globalThis.fetch = (async (url) => {
146
- fetchedUrl = url;
147
- return {
148
- ok: true,
149
- status: 200,
150
- json: async () => mockConfig,
151
- };
152
- });
153
- fetchingAssemblies.add('GCF_009858895.2');
154
- await fetchAssemblyHub(session, 'GCF_009858895.2');
155
- globalThis.fetch = originalFetch;
156
- assert.strictEqual(fetchedUrl, 'https://jbrowse.org/hubs/genark/GCF/009/858/895/GCF_009858895.2/config.json');
157
- assert.strictEqual(calls.addSessionAssembly.length, 1);
158
- });
159
- it('fetches GenArk assembly with GCA_ prefix', async () => {
160
- const { session, calls } = createMockSession();
161
- const mockConfig = {
162
- assemblies: [{ name: 'GCA_000001405.15', sequence: {} }],
163
- tracks: [],
164
- };
165
- let fetchedUrl = '';
166
- const originalFetch = globalThis.fetch;
167
- globalThis.fetch = (async (url) => {
168
- fetchedUrl = url;
169
- return {
170
- ok: true,
171
- status: 200,
172
- json: async () => mockConfig,
173
- };
174
- });
175
- fetchingAssemblies.add('GCA_000001405.15');
176
- await fetchAssemblyHub(session, 'GCA_000001405.15');
177
- globalThis.fetch = originalFetch;
178
- assert.strictEqual(fetchedUrl, 'https://jbrowse.org/hubs/genark/GCA/000/001/405/GCA_000001405.15/config.json');
179
- assert.strictEqual(calls.addSessionAssembly.length, 1);
180
- });
181
- it('uses UCSC URL for non-GenArk assemblies', async () => {
182
- const { session } = createMockSession();
183
- const mockConfig = {
184
- assemblies: [{ name: 'hg38', sequence: {} }],
185
- tracks: [],
186
- };
187
- let fetchedUrl = '';
188
- const originalFetch = globalThis.fetch;
189
- globalThis.fetch = (async (url) => {
190
- fetchedUrl = url;
191
- return {
192
- ok: true,
193
- status: 200,
194
- json: async () => mockConfig,
195
- };
196
- });
197
- fetchingAssemblies.add('hg38');
198
- await fetchAssemblyHub(session, 'hg38');
199
- globalThis.fetch = originalFetch;
200
- assert.strictEqual(fetchedUrl, 'https://jbrowse.org/ucsc/hg38/config.json');
201
- });
202
- });
203
- //# sourceMappingURL=fetchAssemblyHub.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetchAssemblyHub.test.js","sourceRoot":"","sources":["../src/fetchAssemblyHub.test.ts"],"names":[],"mappings":"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"}
@@ -1,255 +0,0 @@
1
- import assert from 'node:assert'
2
- import { beforeEach, describe, it } from 'node:test'
3
-
4
- import { fetchAssemblyHub, fetchingAssemblies } from './fetchAssemblyHub.ts'
5
-
6
- import type { HubSession } from './fetchAssemblyHub.ts'
7
-
8
- function createMockSession(): {
9
- session: HubSession
10
- calls: {
11
- addSessionAssembly: unknown[]
12
- addTrackConf: unknown[]
13
- notifyError: unknown[]
14
- }
15
- } {
16
- const calls = {
17
- addSessionAssembly: [] as unknown[],
18
- addTrackConf: [] as unknown[],
19
- notifyError: [] as unknown[],
20
- }
21
- return {
22
- session: {
23
- assemblyManager: {
24
- get: (_name: string) => undefined as unknown,
25
- },
26
- addSessionAssembly: (assembly: unknown) => {
27
- calls.addSessionAssembly.push(assembly)
28
- },
29
- addTrackConf: (track: unknown) => {
30
- calls.addTrackConf.push(track)
31
- },
32
- notifyError: (msg: string, e: unknown) => {
33
- calls.notifyError.push({ msg, e })
34
- },
35
- },
36
- calls,
37
- }
38
- }
39
-
40
- function mockFetch(config: unknown, ok = true, status = 200) {
41
- const originalFetch = globalThis.fetch
42
- globalThis.fetch = (async () => ({
43
- ok,
44
- status,
45
- json: async () => config,
46
- })) as unknown as typeof fetch
47
- return () => {
48
- globalThis.fetch = originalFetch
49
- }
50
- }
51
-
52
- describe('fetchAssemblyHub', () => {
53
- beforeEach(() => {
54
- fetchingAssemblies.clear()
55
- })
56
-
57
- it('adds assemblies from fetched config', async () => {
58
- const { session, calls } = createMockSession()
59
- const mockConfig = {
60
- assemblies: [{ name: 'hg38', sequence: {} }],
61
- tracks: [],
62
- }
63
- const restore = mockFetch(mockConfig)
64
-
65
- fetchingAssemblies.add('hg38')
66
- await fetchAssemblyHub(session, 'hg38')
67
- restore()
68
-
69
- assert.strictEqual(calls.addSessionAssembly.length, 1)
70
- assert.deepStrictEqual(calls.addSessionAssembly[0], {
71
- name: 'hg38',
72
- sequence: {},
73
- })
74
- assert.strictEqual(fetchingAssemblies.has('hg38'), false)
75
- })
76
-
77
- it('adds tracks from fetched config', async () => {
78
- const { session, calls } = createMockSession()
79
- const mockConfig = {
80
- assemblies: [],
81
- tracks: [{ trackId: 'genes', type: 'FeatureTrack' }],
82
- }
83
- const restore = mockFetch(mockConfig)
84
-
85
- fetchingAssemblies.add('hg38')
86
- await fetchAssemblyHub(session, 'hg38')
87
- restore()
88
-
89
- assert.strictEqual(calls.addTrackConf.length, 1)
90
- assert.deepStrictEqual(calls.addTrackConf[0], {
91
- trackId: 'genes',
92
- type: 'FeatureTrack',
93
- })
94
- })
95
-
96
- it('skips assembly if already exists', async () => {
97
- const { session, calls } = createMockSession()
98
- session.assemblyManager.get = () => ({ name: 'hg38' })
99
- const mockConfig = {
100
- assemblies: [{ name: 'hg38', sequence: {} }],
101
- tracks: [],
102
- }
103
- const restore = mockFetch(mockConfig)
104
-
105
- fetchingAssemblies.add('hg38')
106
- await fetchAssemblyHub(session, 'hg38')
107
- restore()
108
-
109
- assert.strictEqual(calls.addSessionAssembly.length, 0)
110
- })
111
-
112
- it('handles fetch error gracefully', async () => {
113
- const { session, calls } = createMockSession()
114
- const restore = mockFetch({}, false, 404)
115
-
116
- fetchingAssemblies.add('nonexistent')
117
- await fetchAssemblyHub(session, 'nonexistent')
118
- restore()
119
-
120
- assert.strictEqual(calls.notifyError.length, 1)
121
- assert.strictEqual(fetchingAssemblies.has('nonexistent'), false)
122
- })
123
-
124
- it('handles network error gracefully', async () => {
125
- const { session, calls } = createMockSession()
126
- const originalFetch = globalThis.fetch
127
- globalThis.fetch = (async () => {
128
- throw new Error('Network error')
129
- }) as unknown as typeof fetch
130
-
131
- fetchingAssemblies.add('hg38')
132
- await fetchAssemblyHub(session, 'hg38')
133
- globalThis.fetch = originalFetch
134
-
135
- assert.strictEqual(calls.notifyError.length, 1)
136
- assert.strictEqual(fetchingAssemblies.has('hg38'), false)
137
- })
138
-
139
- it('cleans up fetchingAssemblies on success', async () => {
140
- const { session } = createMockSession()
141
- const mockConfig = { assemblies: [], tracks: [] }
142
- const restore = mockFetch(mockConfig)
143
-
144
- fetchingAssemblies.add('hg38')
145
- assert.strictEqual(fetchingAssemblies.has('hg38'), true)
146
-
147
- await fetchAssemblyHub(session, 'hg38')
148
- restore()
149
-
150
- assert.strictEqual(fetchingAssemblies.has('hg38'), false)
151
- })
152
- })
153
-
154
- describe('extension point handler', () => {
155
- it('fetchingAssemblies prevents duplicate requests', () => {
156
- fetchingAssemblies.add('hg38')
157
- const shouldFetch = !fetchingAssemblies.has('hg38')
158
- assert.strictEqual(shouldFetch, false)
159
- })
160
-
161
- it('allows fetch for new assembly', () => {
162
- fetchingAssemblies.clear()
163
- const shouldFetch = !fetchingAssemblies.has('hg38')
164
- assert.strictEqual(shouldFetch, true)
165
- })
166
- })
167
-
168
- describe('GenArk assembly support', () => {
169
- beforeEach(() => {
170
- fetchingAssemblies.clear()
171
- })
172
-
173
- it('fetches GenArk assembly with GCF_ prefix', async () => {
174
- const { session, calls } = createMockSession()
175
- const mockConfig = {
176
- assemblies: [{ name: 'GCF_009858895.2', sequence: {} }],
177
- tracks: [],
178
- }
179
-
180
- let fetchedUrl = ''
181
- const originalFetch = globalThis.fetch
182
- globalThis.fetch = (async (url: string) => {
183
- fetchedUrl = url
184
- return {
185
- ok: true,
186
- status: 200,
187
- json: async () => mockConfig,
188
- }
189
- }) as unknown as typeof fetch
190
-
191
- fetchingAssemblies.add('GCF_009858895.2')
192
- await fetchAssemblyHub(session, 'GCF_009858895.2')
193
- globalThis.fetch = originalFetch
194
-
195
- assert.strictEqual(
196
- fetchedUrl,
197
- 'https://jbrowse.org/hubs/genark/GCF/009/858/895/GCF_009858895.2/config.json',
198
- )
199
- assert.strictEqual(calls.addSessionAssembly.length, 1)
200
- })
201
-
202
- it('fetches GenArk assembly with GCA_ prefix', async () => {
203
- const { session, calls } = createMockSession()
204
- const mockConfig = {
205
- assemblies: [{ name: 'GCA_000001405.15', sequence: {} }],
206
- tracks: [],
207
- }
208
-
209
- let fetchedUrl = ''
210
- const originalFetch = globalThis.fetch
211
- globalThis.fetch = (async (url: string) => {
212
- fetchedUrl = url
213
- return {
214
- ok: true,
215
- status: 200,
216
- json: async () => mockConfig,
217
- }
218
- }) as unknown as typeof fetch
219
-
220
- fetchingAssemblies.add('GCA_000001405.15')
221
- await fetchAssemblyHub(session, 'GCA_000001405.15')
222
- globalThis.fetch = originalFetch
223
-
224
- assert.strictEqual(
225
- fetchedUrl,
226
- 'https://jbrowse.org/hubs/genark/GCA/000/001/405/GCA_000001405.15/config.json',
227
- )
228
- assert.strictEqual(calls.addSessionAssembly.length, 1)
229
- })
230
-
231
- it('uses UCSC URL for non-GenArk assemblies', async () => {
232
- const { session } = createMockSession()
233
- const mockConfig = {
234
- assemblies: [{ name: 'hg38', sequence: {} }],
235
- tracks: [],
236
- }
237
-
238
- let fetchedUrl = ''
239
- const originalFetch = globalThis.fetch
240
- globalThis.fetch = (async (url: string) => {
241
- fetchedUrl = url
242
- return {
243
- ok: true,
244
- status: 200,
245
- json: async () => mockConfig,
246
- }
247
- }) as unknown as typeof fetch
248
-
249
- fetchingAssemblies.add('hg38')
250
- await fetchAssemblyHub(session, 'hg38')
251
- globalThis.fetch = originalFetch
252
-
253
- assert.strictEqual(fetchedUrl, 'https://jbrowse.org/ucsc/hg38/config.json')
254
- })
255
- })
@@ -1,68 +0,0 @@
1
- // Track assemblies we're currently fetching to avoid duplicate requests
2
- export const fetchingAssemblies = new Set<string>()
3
-
4
- export interface HubSession {
5
- assemblyManager: { get: (name: string) => unknown }
6
- addSessionAssembly: (assembly: unknown) => void
7
- addTrackConf: (track: unknown) => void
8
- notifyError: (msg: string, e: unknown) => void
9
- }
10
-
11
- function getGenArkConfigUrl(accession: string) {
12
- const [base, rest] = accession.split('_')
13
- if (!rest) {
14
- return undefined
15
- }
16
- const match = rest.match(/.{1,3}/g)
17
- if (!match || match.length < 3) {
18
- return undefined
19
- }
20
- const [b1, b2, b3] = match
21
- return `https://jbrowse.org/hubs/genark/${base}/${b1}/${b2}/${b3}/${accession}/config.json`
22
- }
23
-
24
- function getConfigUrl(assemblyName: string) {
25
- // GenArk assemblies (GCA_* or GCF_*)
26
- if (assemblyName.startsWith('GCA_') || assemblyName.startsWith('GCF_')) {
27
- return getGenArkConfigUrl(assemblyName)
28
- }
29
- // UCSC assemblies (hg38, mm10, etc.)
30
- return `https://jbrowse.org/ucsc/${assemblyName}/config.json`
31
- }
32
-
33
- export async function fetchAssemblyHub(
34
- session: HubSession,
35
- assemblyName: string,
36
- ) {
37
- const url = getConfigUrl(assemblyName)
38
- if (!url) {
39
- return
40
- }
41
- try {
42
- const response = await fetch(url)
43
- if (!response.ok) {
44
- throw new Error(`HTTP ${response.status} from ${url}`)
45
- }
46
- const config = (await response.json()) as {
47
- assemblies?: { name: string }[]
48
- tracks?: unknown[]
49
- }
50
-
51
- // Add assemblies directly to session
52
- for (const assembly of config.assemblies ?? []) {
53
- if (!session.assemblyManager.get(assembly.name)) {
54
- session.addSessionAssembly(assembly)
55
- }
56
- }
57
-
58
- // Add tracks directly to session (addTrackConf handles duplicates)
59
- for (const track of config.tracks ?? []) {
60
- session.addTrackConf(track)
61
- }
62
- } catch (e) {
63
- console.error(e)
64
- session.notifyError(`Failed to load assembly ${assemblyName}: ${e}`, e)
65
- } finally {
66
- fetchingAssemblies.delete(assemblyName)
67
- }
68
- }