@cmdcolin/jbrowse-plugin-hubs 1.0.2 → 1.0.3
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/fetchAssemblyHub.d.ts +10 -0
- package/dist/fetchAssemblyHub.js +53 -0
- package/dist/fetchAssemblyHub.js.map +1 -0
- package/dist/fetchAssemblyHub.test.d.ts +1 -0
- package/dist/fetchAssemblyHub.test.js +203 -0
- package/dist/fetchAssemblyHub.test.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +14 -14
- package/dist/index.js.map +1 -1
- package/dist/jbrowse-plugin-hubs.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-hubs.umd.production.min.js.map +4 -4
- package/package.json +23 -36
- package/src/fetchAssemblyHub.test.ts +255 -0
- package/src/fetchAssemblyHub.ts +68 -0
- package/src/index.ts +27 -20
|
@@ -0,0 +1,10 @@
|
|
|
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>;
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchAssemblyHub.test.js","sourceRoot":"","sources":["../src/fetchAssemblyHub.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
4
4
|
name: string;
|
|
5
5
|
version: any;
|
|
6
6
|
install(pluginManager: PluginManager): void;
|
|
7
|
-
configure(
|
|
7
|
+
configure(_pluginManager: PluginManager): void;
|
|
8
8
|
}
|
|
9
|
+
export { type HubSession, fetchAssemblyHub, fetchingAssemblies, } from './fetchAssemblyHub.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Plugin from '@jbrowse/core/Plugin';
|
|
2
|
+
import { fetchAssemblyHub, fetchingAssemblies } from "./fetchAssemblyHub.js";
|
|
2
3
|
import { version } from '../package.json';
|
|
3
4
|
export default class HubsViewerPlugin extends Plugin {
|
|
4
5
|
name = 'HubsViewerPlugin';
|
|
@@ -7,22 +8,21 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
7
8
|
pluginManager.addToExtensionPoint('Core-handleUnrecognizedAssembly', (_defaultResult, args) => {
|
|
8
9
|
const session = args.session;
|
|
9
10
|
const assemblyName = args.assemblyName;
|
|
10
|
-
|
|
11
|
-
if (assemblyName
|
|
12
|
-
|
|
13
|
-
console.log('getUnrecognizedAssembly', { assemblyName });
|
|
14
|
-
const conf = {
|
|
15
|
-
type: 'JB2TrackHubConnection',
|
|
16
|
-
uri: `https://jbrowse.org/ucsc/${assemblyName}/config.json`,
|
|
17
|
-
name: `conn_${assemblyName}`,
|
|
18
|
-
assemblyNames: [assemblyName],
|
|
19
|
-
connectionId: jb2asm,
|
|
20
|
-
};
|
|
21
|
-
session.addConnectionConf(conf);
|
|
22
|
-
session.makeConnection(conf);
|
|
11
|
+
// Skip if missing required args
|
|
12
|
+
if (!session || !assemblyName) {
|
|
13
|
+
return;
|
|
23
14
|
}
|
|
15
|
+
// Skip if already fetching (must check this first to prevent recursion)
|
|
16
|
+
if (fetchingAssemblies.has(assemblyName)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
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);
|
|
24
23
|
});
|
|
25
24
|
}
|
|
26
|
-
configure(
|
|
25
|
+
configure(_pluginManager) { }
|
|
27
26
|
}
|
|
27
|
+
export { fetchAssemblyHub, fetchingAssemblies, } from "./fetchAssemblyHub.js";
|
|
28
28
|
//# 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,OAAO,EAAE,MAAM,iBAAiB,CAAA;
|
|
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,2 +1,2 @@
|
|
|
1
|
-
"use strict";var JBrowsePluginHubs=(()=>{var g=Object.create;var a=Object.defineProperty;var
|
|
1
|
+
"use strict";var JBrowsePluginHubs=(()=>{var g=Object.create;var a=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var w=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 h(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(w(e)):{},u(s||!e||!e.__esModule?a(n,"default",{value:e,enumerable:!0}):n,e)),v=e=>u(a({},"__esModule",{value:!0}),e);var m=j((M,d)=>{d.exports=JBrowseExports["@jbrowse/core/Plugin"]});var S={};k(S,{default:()=>c,fetchAssemblyHub:()=>l,fetchingAssemblies:()=>o});var f=A(m());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 b="1.0.3";var c=class extends f.default{name="HubsViewerPlugin";version=b;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);})();
|
|
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", "../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\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
|
|
5
|
-
"mappings": "gpBAAA,IAAAA,EAAAC,EAAA,CAAAC,EAAAC,IAAA,CAAAA,EAAO,QAAU,eAAe,sBAAsB,ICAtD,IAAAC,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,IAAAC,EAAmB,
|
|
6
|
-
"names": ["require_Plugin", "__commonJSMin", "exports", "module", "index_exports", "__export", "HubsViewerPlugin", "import_Plugin", "version", "HubsViewerPlugin", "Plugin", "version", "pluginManager", "_defaultResult", "args", "session", "assemblyName", "
|
|
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.3\",\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/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 \"mobx-state-tree\": \"^7.0.2\",\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"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.3",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@cmdcolin/jbrowse-plugin-hubs",
|
|
5
5
|
"keywords": [
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"clean": "rimraf dist",
|
|
18
|
-
"start": "node esbuild.mjs",
|
|
19
|
-
"test": "
|
|
18
|
+
"start": "node esbuild.mjs --watch",
|
|
19
|
+
"test": "node --experimental-strip-types --test src/fetchAssemblyHub.test.ts",
|
|
20
20
|
"format": "prettier --write .",
|
|
21
21
|
"prebuild": "npm run clean",
|
|
22
22
|
"build": "tsc && NODE_ENV=production node esbuild.mjs",
|
|
@@ -25,51 +25,38 @@
|
|
|
25
25
|
"postversion": "git push --follow-tags"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@babel/core": "^7.
|
|
28
|
+
"@babel/core": "^7.28.6",
|
|
29
29
|
"@babel/preset-react": "^7.10.4",
|
|
30
30
|
"@emotion/react": "^11.10.4",
|
|
31
|
+
"@emotion/styled": "^11.14.1",
|
|
32
|
+
"@eslint/js": "^9.39.2",
|
|
31
33
|
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
|
32
|
-
"@jbrowse/core": "^
|
|
33
|
-
"@jbrowse/plugin-data-management": "^
|
|
34
|
-
"@jbrowse/plugin-linear-genome-view": "^
|
|
35
|
-
"@mui/material": "^7.
|
|
36
|
-
"@mui/system": "^7.
|
|
37
|
-
"@mui/x-data-grid": "^8.
|
|
34
|
+
"@jbrowse/core": "^4.1.3",
|
|
35
|
+
"@jbrowse/plugin-data-management": "^4.1.3",
|
|
36
|
+
"@jbrowse/plugin-linear-genome-view": "^4.1.3",
|
|
37
|
+
"@mui/material": "^7.3.7",
|
|
38
|
+
"@mui/system": "^7.3.7",
|
|
39
|
+
"@mui/x-data-grid": "^8.26.0",
|
|
38
40
|
"@types/d3-array": "^3.2.1",
|
|
39
41
|
"@types/d3-hierarchy": "^3.1.7",
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"@types/react": "^19.
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"eslint": "^9.17.0",
|
|
42
|
+
"@types/node": "^25.1.0",
|
|
43
|
+
"@types/react": "^19.2.10",
|
|
44
|
+
"esbuild": "^0.27.2",
|
|
45
|
+
"eslint": "^9.39.2",
|
|
45
46
|
"eslint-plugin-import": "^2.31.0",
|
|
46
|
-
"eslint-plugin-
|
|
47
|
-
"eslint-plugin-react-hooks": "^5.1.0",
|
|
48
|
-
"eslint-plugin-unicorn": "^60.0.0",
|
|
47
|
+
"eslint-plugin-unicorn": "^62.0.0",
|
|
49
48
|
"mobx": "^6.0.0",
|
|
50
49
|
"mobx-react": "^9.0.1",
|
|
51
|
-
"mobx-state-tree": "^
|
|
52
|
-
"prettier": "^3.
|
|
50
|
+
"mobx-state-tree": "^7.0.2",
|
|
51
|
+
"prettier": "^3.8.1",
|
|
53
52
|
"pretty-bytes": "^7.0.0",
|
|
54
|
-
"react": "^19.
|
|
55
|
-
"react-dom": "^19.
|
|
56
|
-
"rimraf": "^6.
|
|
53
|
+
"react": "^19.2.4",
|
|
54
|
+
"react-dom": "^19.2.4",
|
|
55
|
+
"rimraf": "^6.1.2",
|
|
57
56
|
"rxjs": "^7.8.1",
|
|
58
57
|
"serve": "^14.2.0",
|
|
59
|
-
"tss-react": "^4.9.18",
|
|
60
58
|
"typescript": "^5.1.6",
|
|
61
|
-
"typescript-eslint": "^8.
|
|
62
|
-
"vitest": "^3.2.1"
|
|
63
|
-
},
|
|
64
|
-
"dependencies": {
|
|
65
|
-
"@gmod/bgzf-filehandle": "^4.0.0",
|
|
66
|
-
"abortable-promise-cache": "^1.5.0",
|
|
67
|
-
"buffer": "^6.0.3",
|
|
68
|
-
"d3-array": "^3.2.4",
|
|
69
|
-
"d3-hierarchy": "^3.1.2",
|
|
70
|
-
"fast-deep-equal": "^3.1.3",
|
|
71
|
-
"generic-filehandle2": "^2.0.1",
|
|
72
|
-
"long": "^5.2.3"
|
|
59
|
+
"typescript-eslint": "^8.54.0"
|
|
73
60
|
},
|
|
74
61
|
"publishConfig": {
|
|
75
62
|
"access": "public"
|
|
@@ -0,0 +1,255 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
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'
|
|
4
5
|
import { version } from '../package.json'
|
|
5
6
|
|
|
7
|
+
import type { HubSession } from './fetchAssemblyHub.ts'
|
|
8
|
+
|
|
6
9
|
export default class HubsViewerPlugin extends Plugin {
|
|
7
10
|
name = 'HubsViewerPlugin'
|
|
8
11
|
version = version
|
|
@@ -11,29 +14,33 @@ export default class HubsViewerPlugin extends Plugin {
|
|
|
11
14
|
pluginManager.addToExtensionPoint(
|
|
12
15
|
'Core-handleUnrecognizedAssembly',
|
|
13
16
|
(_defaultResult, args) => {
|
|
14
|
-
const session = args.session as
|
|
15
|
-
const assemblyName = args.assemblyName as string
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
type: 'JB2TrackHubConnection',
|
|
26
|
-
uri: `https://jbrowse.org/ucsc/${assemblyName}/config.json`,
|
|
27
|
-
name: `conn_${assemblyName}`,
|
|
28
|
-
assemblyNames: [assemblyName],
|
|
29
|
-
connectionId: jb2asm,
|
|
30
|
-
}
|
|
31
|
-
session.addConnectionConf(conf)
|
|
32
|
-
session.makeConnection(conf)
|
|
17
|
+
const session = args.session as HubSession | undefined
|
|
18
|
+
const assemblyName = args.assemblyName as string | undefined
|
|
19
|
+
|
|
20
|
+
// Skip if missing required args
|
|
21
|
+
if (!session || !assemblyName) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Skip if already fetching (must check this first to prevent recursion)
|
|
26
|
+
if (fetchingAssemblies.has(assemblyName)) {
|
|
27
|
+
return
|
|
33
28
|
}
|
|
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)
|
|
34
35
|
},
|
|
35
36
|
)
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
configure(
|
|
39
|
+
configure(_pluginManager: PluginManager) {}
|
|
39
40
|
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
type HubSession,
|
|
44
|
+
fetchAssemblyHub,
|
|
45
|
+
fetchingAssemblies,
|
|
46
|
+
} from './fetchAssemblyHub.ts'
|