@cmdcolin/jbrowse-plugin-hubs 1.0.11 → 1.0.12

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
@@ -1,7 +1,7 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
2
  import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util';
3
3
  import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree';
4
- import { toDesktopSnapshot } from './util';
4
+ import { saveAs, toDesktopSnapshot } from './util';
5
5
  import { version } from '../package.json';
6
6
  // Serializes the current web session as a desktop `.jbrowse` file. genomes
7
7
  // sessions are config-shaped already, so this just folds the live session into
@@ -9,11 +9,7 @@ import { version } from '../package.json';
9
9
  function downloadDesktopSession(session) {
10
10
  const { jbrowse } = getRoot(session);
11
11
  const snap = toDesktopSnapshot(getSnapshot(jbrowse), getSnapshot(session));
12
- const anchor = document.createElement('a');
13
- anchor.href = URL.createObjectURL(new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }));
14
- anchor.download = 'session.jbrowse';
15
- anchor.click();
16
- URL.revokeObjectURL(anchor.href);
12
+ saveAs(new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }), 'session.jbrowse');
17
13
  }
18
14
  function getGenArkConfigUrl(accession) {
19
15
  const [base, rest] = accession.split('_');
@@ -33,22 +29,26 @@ function getConfigUrl(assemblyName) {
33
29
  }
34
30
  return `https://jbrowse.org/ucsc/${assemblyName}/config.json`;
35
31
  }
36
- export default class HubsViewerPlugin extends Plugin {
37
- name = 'HubsViewerPlugin';
38
- version = version;
39
- install(pluginManager) {
40
- pluginManager.addToExtensionPoint('Core-handleUnrecognizedAssembly', (_defaultResult, args) => {
41
- const session = args.session;
42
- const assemblyName = args.assemblyName;
43
- if (!session || !assemblyName) {
44
- return;
45
- }
46
- const uri = getConfigUrl(assemblyName);
47
- if (!uri) {
48
- return;
49
- }
50
- const connectionId = `jb2hub-${assemblyName}`;
51
- if (!session.connections.find(f => f.connectionId === connectionId)) {
32
+ // Names already being resolved, so the extension point firing repeatedly for
33
+ // the same assembly (it fires on every unresolved read) starts one probe, not
34
+ // one per read. The connection itself is deduped by connectionId, but that
35
+ // check only happens once the probe has come back.
36
+ const pending = new Set();
37
+ // The url is a guess from the name, and plenty of assemblies aren't at it:
38
+ // GenArk strain hubs (mouseStrains and friends) live under
39
+ // /hubs/genark/<hub>/<strain>/, and a session can name an assembly this host
40
+ // has never heard of. Connecting to a config that 404s puts a red error
41
+ // snackbar over a session that is otherwise working, so probe first and stay
42
+ // quiet when there's nothing there — some other handler, or the session
43
+ // itself, may be supplying the assembly.
44
+ async function connectIfConfigExists(session, assemblyName, uri) {
45
+ const connectionId = `jb2hub-${assemblyName}`;
46
+ if (!pending.has(assemblyName) &&
47
+ !session.connections.find(f => f.connectionId === connectionId)) {
48
+ pending.add(assemblyName);
49
+ try {
50
+ const response = await fetch(uri, { method: 'HEAD' });
51
+ if (response.ok) {
52
52
  const conf = {
53
53
  type: 'JB2TrackHubConnection',
54
54
  uri,
@@ -59,6 +59,31 @@ export default class HubsViewerPlugin extends Plugin {
59
59
  session.addConnectionConf(conf);
60
60
  session.makeConnection(conf);
61
61
  }
62
+ }
63
+ finally {
64
+ pending.delete(assemblyName);
65
+ }
66
+ }
67
+ }
68
+ export default class HubsViewerPlugin extends Plugin {
69
+ name = 'HubsViewerPlugin';
70
+ version = version;
71
+ install(pluginManager) {
72
+ pluginManager.addToExtensionPoint('Core-handleUnrecognizedAssembly', (defaultResult, args) => {
73
+ const session = args.session;
74
+ const assemblyName = args.assemblyName;
75
+ const uri = session && assemblyName && getConfigUrl(assemblyName);
76
+ if (session && assemblyName && uri) {
77
+ // the extension point is sync; the probe and the connection it may
78
+ // create land later, which is fine because assemblyManager re-reads
79
+ // reactively once the assembly shows up
80
+ connectIfConfigExists(session, assemblyName, uri).catch((e) => {
81
+ console.error(e);
82
+ });
83
+ }
84
+ // Pass the accumulator through: this extension point chains handlers, so
85
+ // returning undefined would clobber another plugin's result.
86
+ return defaultResult;
62
87
  });
63
88
  }
64
89
  configure(pluginManager) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";var JBrowsePluginHubs=(()=>{var S=Object.create;var d=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,E=Object.prototype.hasOwnProperty;var g=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),I=(n,e)=>{for(var t in e)d(n,t,{get:e[t],enumerable:!0})},b=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of T(e))!E.call(n,s)&&s!==t&&d(n,s,{get:()=>e[s],enumerable:!(o=v(e,s))||o.enumerable});return n};var m=(n,e,t)=>(t=n!=null?S($(n)):{},b(e||!n||!n.__esModule?d(t,"default",{value:n,enumerable:!0}):t,n)),P=n=>b(d({},"__esModule",{value:!0}),n);var k=g((N,w)=>{w.exports=JBrowseExports["@jbrowse/core/Plugin"]});var j=g((G,h)=>{h.exports=JBrowseExports["@jbrowse/core/util"]});var x=g((L,y)=>{y.exports=JBrowseExports["mobx-state-tree"]});var _={};I(_,{default:()=>l});var A=m(k()),p=m(j()),i=m(x());function u(n){return Array.isArray(n)?n:[]}function R(n,e,t){let o=new Map;for(let s of[...n,...e])o.set(s[t],s);return[...o.values()]}function C(n,e){let{sessionTracks:t,sessionAssemblies:o,...s}=e;return{...n,tracks:R(u(n.tracks),u(t),"trackId"),assemblies:R(u(n.assemblies),u(o),"name"),defaultSession:s}}var M="1.0.11";function U(n){let{jbrowse:e}=(0,i.getRoot)(n),t=C((0,i.getSnapshot)(e),(0,i.getSnapshot)(n)),o=document.createElement("a");o.href=URL.createObjectURL(new Blob([JSON.stringify(t,null,2)],{type:"application/json"})),o.download="session.jbrowse",o.click(),URL.revokeObjectURL(o.href)}function B(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,c]=o;return`https://jbrowse.org/hubs/genark/${e}/${s}/${r}/${c}/${n}/config.json`}function O(n){return n.startsWith("GCA_")||n.startsWith("GCF_")?B(n):`https://jbrowse.org/ucsc/${n}/config.json`}var l=class extends A.default{name="HubsViewerPlugin";version=M;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(t,o)=>{let s=o.session,r=o.assemblyName;if(!s||!r)return;let c=O(r);if(!c)return;let f=`jb2hub-${r}`;if(!s.connections.find(a=>a.connectionId===f)){let a={type:"JB2TrackHubConnection",uri:c,name:`conn_${r}`,assemblyNames:[r],connectionId:f};s.addConnectionConf(a),s.makeConnection(a)}})}configure(e){if(!p.isElectron&&(0,p.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 P(_);})();
1
+ "use strict";var JBrowsePluginHubs=(()=>{var v=Object.create;var a=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var T=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty;var u=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),$=(n,e)=>{for(var o in e)a(n,o,{get:e[o],enumerable:!0})},m=(n,e,o,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of I(e))!D.call(n,s)&&s!==o&&a(n,s,{get:()=>e[s],enumerable:!(t=E(e,s))||t.enumerable});return n};var g=(n,e,o)=>(o=n!=null?v(T(n)):{},m(e||!n||!n.__esModule?a(o,"default",{value:n,enumerable:!0}):o,n)),B=n=>m(a({},"__esModule",{value:!0}),n);var w=u((H,b)=>{b.exports=JBrowseExports["@jbrowse/core/Plugin"]});var h=u((W,k)=>{k.exports=JBrowseExports["@jbrowse/core/util"]});var x=u((z,j)=>{j.exports=JBrowseExports["mobx-state-tree"]});var G={};$(G,{default:()=>l});var S=g(w()),p=g(h()),i=g(x());function A(n,e){let o=URL.createObjectURL(n),t=document.createElement("a");t.href=o,t.download=e,t.rel="noopener",t.click(),setTimeout(()=>{URL.revokeObjectURL(o)},4e4)}function d(n){return Array.isArray(n)?n:[]}function y(n,e,o){let t=new Map;for(let s of[...n,...e])t.set(s[o],s);return[...t.values()]}function R(n,e){let{sessionTracks:o,sessionAssemblies:t,...s}=e;return{...n,tracks:y(d(n.tracks),d(o),"trackId"),assemblies:y(d(n.assemblies),d(t),"name"),defaultSession:s}}var C="1.0.12";function U(n){let{jbrowse:e}=(0,i.getRoot)(n),o=R((0,i.getSnapshot)(e),(0,i.getSnapshot)(n));A(new Blob([JSON.stringify(o,null,2)],{type:"application/json"}),"session.jbrowse")}function O(n){let[e,o]=n.split("_");if(!o)return;let t=o.match(/.{1,3}/g);if(!t||t.length<3)return;let[s,r,c]=t;return`https://jbrowse.org/hubs/genark/${e}/${s}/${r}/${c}/${n}/config.json`}function J(n){return n.startsWith("GCA_")||n.startsWith("GCF_")?O(n):`https://jbrowse.org/ucsc/${n}/config.json`}var f=new Set;async function _(n,e,o){let t=`jb2hub-${e}`;if(!f.has(e)&&!n.connections.find(s=>s.connectionId===t)){f.add(e);try{if((await fetch(o,{method:"HEAD"})).ok){let r={type:"JB2TrackHubConnection",uri:o,name:`conn_${e}`,assemblyNames:[e],connectionId:t};n.addConnectionConf(r),n.makeConnection(r)}}finally{f.delete(e)}}}var l=class extends S.default{name="HubsViewerPlugin";version=C;install(e){e.addToExtensionPoint("Core-handleUnrecognizedAssembly",(o,t)=>{let s=t.session,r=t.assemblyName,c=s&&r&&J(r);return s&&r&&c&&_(s,r,c).catch(M=>{console.error(M)}),o})}configure(e){if(!p.isElectron&&(0,p.isAbstractMenuManager)(e.rootModel))try{e.rootModel.appendToMenu("Tools",{label:"Download desktop session (.jbrowse)",onClick:o=>{U(o)}})}catch(o){console.error("could not add the desktop-session download item",o)}}};return B(G);})();
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 { 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 const anchor = document.createElement('a')\n anchor.href = URL.createObjectURL(\n new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),\n )\n anchor.download = 'session.jbrowse'\n anchor.click()\n URL.revokeObjectURL(anchor.href)\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 // 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", "export async function textfetch(url: string, arg?: RequestInit) {\n const res = await fetch(url, arg)\n if (!res.ok) {\n throw new Error(`HTTP ${res.status} from ${url}`)\n }\n return res.text()\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.11\",\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": "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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", "asObjArray", "value", "mergeById", "base", "overrides", "key", "merged", "item", "toDesktopSnapshot", "jbrowse", "session", "sessionTracks", "sessionAssemblies", "defaultSession", "version", "downloadDesktopSession", "session", "jbrowse", "snap", "toDesktopSnapshot", "anchor", "getGenArkConfigUrl", "accession", "base", "rest", "match", "b1", "b2", "b3", "getConfigUrl", "assemblyName", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "_defaultResult", "args", "uri", "connectionId", "f", "conf", "e"]
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": "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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", "getConfigUrl", "assemblyName", "pending", "connectIfConfigExists", "uri", "connectionId", "f", "conf", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "defaultResult", "args", "e"]
7
7
  }
package/dist/util.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare function textfetch(url: string, arg?: RequestInit): Promise<string>;
1
+ export declare function saveAs(blob: Blob, filename: string): void;
2
2
  export declare function toDesktopSnapshot(jbrowse: Record<string, unknown>, session: Record<string, unknown>): Record<string, unknown>;
package/dist/util.js CHANGED
@@ -1,9 +1,17 @@
1
- export async function textfetch(url, arg) {
2
- const res = await fetch(url, arg);
3
- if (!res.ok) {
4
- throw new Error(`HTTP ${res.status} from ${url}`);
5
- }
6
- return res.text();
1
+ // Trigger a browser download of `blob` as `filename`. Mirrors the anchor-download
2
+ // path of @jbrowse/core's saveAs: the deferred revoke avoids canceling the
3
+ // download before the browser has read the blob (the read is async in some
4
+ // browsers). Inlined until a published core release exports saveAs from its util.
5
+ export function saveAs(blob, filename) {
6
+ const url = URL.createObjectURL(blob);
7
+ const anchor = document.createElement('a');
8
+ anchor.href = url;
9
+ anchor.download = filename;
10
+ anchor.rel = 'noopener';
11
+ anchor.click();
12
+ setTimeout(() => {
13
+ URL.revokeObjectURL(url);
14
+ }, 40000);
7
15
  }
8
16
  function asObjArray(value) {
9
17
  return Array.isArray(value) ? value : [];
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW,EAAE,GAAiB;IAC5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACjC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;AACnB,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAmC,CAAC,CAAC,CAAC,EAAE,CAAA;AACzE,CAAC;AAED,+EAA+E;AAC/E,+EAA+E;AAC/E,8BAA8B;AAC9B,SAAS,SAAS,CAChB,IAA+B,EAC/B,SAAoC,EACpC,GAAW;IAEX,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAA;IAC1D,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,wEAAwE;AACxE,MAAM,UAAU,iBAAiB,CAC/B,OAAgC,EAChC,OAAgC;IAEhC,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,CAAA;IACvE,OAAO;QACL,GAAG,OAAO;QACV,MAAM,EAAE,SAAS,CACf,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAC1B,UAAU,CAAC,aAAa,CAAC,EACzB,SAAS,CACV;QACD,UAAU,EAAE,SAAS,CACnB,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,EAC9B,UAAU,CAAC,iBAAiB,CAAC,EAC7B,MAAM,CACP;QACD,cAAc;KACf,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,2EAA2E;AAC3E,2EAA2E;AAC3E,kFAAkF;AAClF,MAAM,UAAU,MAAM,CAAC,IAAU,EAAE,QAAgB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IAC1C,MAAM,CAAC,IAAI,GAAG,GAAG,CAAA;IACjB,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,MAAM,CAAC,GAAG,GAAG,UAAU,CAAA;IACvB,MAAM,CAAC,KAAK,EAAE,CAAA;IACd,UAAU,CAAC,GAAG,EAAE;QACd,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC,EAAE,KAAK,CAAC,CAAA;AACX,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAmC,CAAC,CAAC,CAAC,EAAE,CAAA;AACzE,CAAC;AAED,+EAA+E;AAC/E,+EAA+E;AAC/E,8BAA8B;AAC9B,SAAS,SAAS,CAChB,IAA+B,EAC/B,SAAoC,EACpC,GAAW;IAEX,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAA;IAC1D,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,wEAAwE;AACxE,MAAM,UAAU,iBAAiB,CAC/B,OAAgC,EAChC,OAAgC;IAEhC,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,CAAA;IACvE,OAAO;QACL,GAAG,OAAO;QACV,MAAM,EAAE,SAAS,CACf,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAC1B,UAAU,CAAC,aAAa,CAAC,EACzB,SAAS,CACV;QACD,UAAU,EAAE,SAAS,CACnB,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,EAC9B,UAAU,CAAC,iBAAiB,CAAC,EAC7B,MAAM,CACP;QACD,cAAc;KACf,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.11",
2
+ "version": "1.0.12",
3
3
  "license": "MIT",
4
4
  "name": "@cmdcolin/jbrowse-plugin-hubs",
5
5
  "repository": {
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import Plugin from '@jbrowse/core/Plugin'
2
2
  import { isAbstractMenuManager, isElectron } from '@jbrowse/core/util'
3
3
  import { getRoot, getSnapshot } from '@jbrowse/mobx-state-tree'
4
4
 
5
- import { toDesktopSnapshot } from './util'
5
+ import { saveAs, toDesktopSnapshot } from './util'
6
6
  import { version } from '../package.json'
7
7
 
8
8
  import type PluginManager from '@jbrowse/core/PluginManager'
@@ -24,13 +24,10 @@ function downloadDesktopSession(session: AbstractSessionModel) {
24
24
  getSnapshot<Record<string, unknown>>(jbrowse),
25
25
  getSnapshot<Record<string, unknown>>(session),
26
26
  )
27
- const anchor = document.createElement('a')
28
- anchor.href = URL.createObjectURL(
27
+ saveAs(
29
28
  new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }),
29
+ 'session.jbrowse',
30
30
  )
31
- anchor.download = 'session.jbrowse'
32
- anchor.click()
33
- URL.revokeObjectURL(anchor.href)
34
31
  }
35
32
 
36
33
  function getGenArkConfigUrl(accession: string) {
@@ -53,6 +50,49 @@ function getConfigUrl(assemblyName: string) {
53
50
  return `https://jbrowse.org/ucsc/${assemblyName}/config.json`
54
51
  }
55
52
 
53
+ // Names already being resolved, so the extension point firing repeatedly for
54
+ // the same assembly (it fires on every unresolved read) starts one probe, not
55
+ // one per read. The connection itself is deduped by connectionId, but that
56
+ // check only happens once the probe has come back.
57
+ const pending = new Set<string>()
58
+
59
+ // The url is a guess from the name, and plenty of assemblies aren't at it:
60
+ // GenArk strain hubs (mouseStrains and friends) live under
61
+ // /hubs/genark/<hub>/<strain>/, and a session can name an assembly this host
62
+ // has never heard of. Connecting to a config that 404s puts a red error
63
+ // snackbar over a session that is otherwise working, so probe first and stay
64
+ // quiet when there's nothing there — some other handler, or the session
65
+ // itself, may be supplying the assembly.
66
+ async function connectIfConfigExists(
67
+ session: Session,
68
+ assemblyName: string,
69
+ uri: string,
70
+ ) {
71
+ const connectionId = `jb2hub-${assemblyName}`
72
+ if (
73
+ !pending.has(assemblyName) &&
74
+ !session.connections.find(f => f.connectionId === connectionId)
75
+ ) {
76
+ pending.add(assemblyName)
77
+ try {
78
+ const response = await fetch(uri, { method: 'HEAD' })
79
+ if (response.ok) {
80
+ const conf = {
81
+ type: 'JB2TrackHubConnection',
82
+ uri,
83
+ name: `conn_${assemblyName}`,
84
+ assemblyNames: [assemblyName],
85
+ connectionId,
86
+ }
87
+ session.addConnectionConf(conf)
88
+ session.makeConnection(conf)
89
+ }
90
+ } finally {
91
+ pending.delete(assemblyName)
92
+ }
93
+ }
94
+ }
95
+
56
96
  export default class HubsViewerPlugin extends Plugin {
57
97
  name = 'HubsViewerPlugin'
58
98
  version = version
@@ -60,28 +100,23 @@ export default class HubsViewerPlugin extends Plugin {
60
100
  install(pluginManager: PluginManager) {
61
101
  pluginManager.addToExtensionPoint(
62
102
  'Core-handleUnrecognizedAssembly',
63
- (_defaultResult, args) => {
103
+ (defaultResult, args) => {
64
104
  const session = args.session as Session | undefined
65
105
  const assemblyName = args.assemblyName as string | undefined
66
- if (!session || !assemblyName) {
67
- return
68
- }
69
- const uri = getConfigUrl(assemblyName)
70
- if (!uri) {
71
- return
72
- }
73
- const connectionId = `jb2hub-${assemblyName}`
74
- if (!session.connections.find(f => f.connectionId === connectionId)) {
75
- const conf = {
76
- type: 'JB2TrackHubConnection',
77
- uri,
78
- name: `conn_${assemblyName}`,
79
- assemblyNames: [assemblyName],
80
- connectionId,
81
- }
82
- session.addConnectionConf(conf)
83
- session.makeConnection(conf)
106
+ const uri = session && assemblyName && getConfigUrl(assemblyName)
107
+ if (session && assemblyName && uri) {
108
+ // the extension point is sync; the probe and the connection it may
109
+ // create land later, which is fine because assemblyManager re-reads
110
+ // reactively once the assembly shows up
111
+ connectIfConfigExists(session, assemblyName, uri).catch(
112
+ (e: unknown) => {
113
+ console.error(e)
114
+ },
115
+ )
84
116
  }
117
+ // Pass the accumulator through: this extension point chains handlers, so
118
+ // returning undefined would clobber another plugin's result.
119
+ return defaultResult
85
120
  },
86
121
  )
87
122
  }
package/src/util.ts CHANGED
@@ -1,9 +1,17 @@
1
- export async function textfetch(url: string, arg?: RequestInit) {
2
- const res = await fetch(url, arg)
3
- if (!res.ok) {
4
- throw new Error(`HTTP ${res.status} from ${url}`)
5
- }
6
- return res.text()
1
+ // Trigger a browser download of `blob` as `filename`. Mirrors the anchor-download
2
+ // path of @jbrowse/core's saveAs: the deferred revoke avoids canceling the
3
+ // download before the browser has read the blob (the read is async in some
4
+ // browsers). Inlined until a published core release exports saveAs from its util.
5
+ export function saveAs(blob: Blob, filename: string) {
6
+ const url = URL.createObjectURL(blob)
7
+ const anchor = document.createElement('a')
8
+ anchor.href = url
9
+ anchor.download = filename
10
+ anchor.rel = 'noopener'
11
+ anchor.click()
12
+ setTimeout(() => {
13
+ URL.revokeObjectURL(url)
14
+ }, 40000)
7
15
  }
8
16
 
9
17
  function asObjArray(value: unknown): Record<string, unknown>[] {