UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

329 lines (328 loc) 11.9 kB
import AbortablePromiseCache from '@gmod/abortable-promise-cache'; import { addDisposer, getParent, getSnapshot, types, } from '@jbrowse/mobx-state-tree'; import { autorun } from 'mobx'; import { getConf } from "../configuration/index.js"; import { adapterConfigCacheKey } from "../data_adapters/util.js"; import QuickLRU from "../util/QuickLRU/index.js"; import { when } from "../util/index.js"; const refNameRegex = new RegExp('[0-9A-Za-z!#$%&+./:;?@^_|~-][0-9A-Za-z!#$%&*+./:;=?@^_|~-]*'); const refNameColors = [ 'rgb(153, 102, 0)', 'rgb(102, 102, 0)', 'rgb(153, 153, 30)', 'rgb(204, 0, 0)', 'rgb(255, 0, 0)', 'rgb(255, 0, 204)', 'rgb(165, 132, 132)', 'rgb(204, 122, 0)', 'rgb(178, 142, 0)', 'rgb(153, 153, 0)', 'rgb(122, 153, 0)', 'rgb(0, 165, 0)', 'rgb(53, 128, 0)', 'rgb(0, 0, 204)', 'rgb(96, 145, 242)', 'rgb(107, 142, 178)', 'rgb(0, 165, 165)', 'rgb(122, 153, 153)', 'rgb(153, 0, 204)', 'rgb(204, 51, 255)', 'rgb(173, 130, 216)', 'rgb(102, 102, 102)', 'rgb(145, 145, 145)', 'rgb(142, 142, 142)', 'rgb(142, 142, 107)', 'rgb(96, 163, 48)', ]; async function loadRefNameMap(assembly, adapterConfig, options, stopToken) { const { sessionId, sequenceAdapter } = options; await when(() => !!(assembly.regions && assembly.refNameAliases), { name: 'when assembly ready', }); const refNames = (await assembly.rpcManager.call(sessionId || 'assemblyRpc', 'CoreGetRefNames', { adapterConfig, sequenceAdapter, stopToken, ...options, }, { timeout: 1000000 })); const { refNameAliases } = assembly; if (!refNameAliases) { throw new Error(`error loading assembly ${assembly.name}'s refNameAliases`); } const refNameMap = Object.fromEntries(refNames.map(name => { checkRefName(name); return [assembly.getCanonicalRefName(name), name]; })); return { forwardMap: refNameMap, reverseMap: Object.fromEntries(Object.entries(refNameMap).map(([canonicalName, adapterName]) => [ adapterName, canonicalName, ])), }; } function checkRefName(refName) { if (!refNameRegex.test(refName)) { throw new Error(`Encountered invalid refName: "${refName}"`); } } export default function assemblyFactory(assemblyConfigType, pluginManager) { const adapterLoads = new AbortablePromiseCache({ cache: new QuickLRU({ maxSize: 1000 }), async fill(args, _stopToken, statusCallback) { const { adapterConf, self, options } = args; let sequenceAdapter; try { const adapterConfig = self.configuration?.sequence?.adapter; sequenceAdapter = adapterConfig ? getSnapshot(adapterConfig) : undefined; } catch (e) { } return loadRefNameMap(self, adapterConf, { ...options, statusCallback, sequenceAdapter, }); }, }); return types .model({ configuration: types.safeReference(assemblyConfigType), }) .volatile(() => ({ error: undefined, loadingP: undefined, volatileRegions: undefined, refNameAliases: undefined, canonicalToSeqAdapterRefNames: undefined, cytobands: undefined, })) .views(self => ({ getConf(arg) { return self.configuration ? getConf(self, arg) : undefined; }, get lowerCaseRefNameAliases() { return self.refNameAliases ? Object.fromEntries(Object.entries(self.refNameAliases).map(([key, val]) => { return [key.toLowerCase(), val]; })) : undefined; }, })) .views(self => ({ get initialized() { self.load(); return !!self.refNameAliases; }, get name() { return self.getConf('name') || ''; }, get regions() { self.load(); return self.volatileRegions; }, get aliases() { return self.getConf('aliases') || []; }, get displayName() { return self.getConf('displayName') || self.getConf('name') || ''; }, hasName(name) { return this.allAliases.includes(name); }, get allAliases() { return [this.name, ...this.aliases]; }, get allRefNames() { return !self.refNameAliases ? undefined : Object.keys(self.refNameAliases); }, get lowerCaseRefNames() { return !self.lowerCaseRefNameAliases ? undefined : Object.keys(self.lowerCaseRefNameAliases); }, get allRefNamesWithLowerCase() { return this.allRefNames && this.lowerCaseRefNames ? [...new Set([...this.allRefNames, ...this.lowerCaseRefNames])] : undefined; }, get rpcManager() { return getParent(self, 2).rpcManager; }, get refNameColors() { const colors = self.getConf('refNameColors') || []; return colors.length === 0 ? refNameColors : colors; }, })) .views(self => ({ get refNames() { return self.regions?.map(region => region.refName); }, })) .views(self => ({ getCanonicalRefName(refName) { if (!self.refNameAliases || !self.lowerCaseRefNameAliases) { throw new Error('aliases not loaded, we expect them to be loaded before getCanonicalRefName can be called'); } return (self.refNameAliases[refName] || self.lowerCaseRefNameAliases[refName]); }, getCanonicalRefName2(refName) { return this.getCanonicalRefName(refName) || refName; }, getRefNameColor(refName) { if (!self.refNames) { return undefined; } const idx = self.refNames.indexOf(refName); return idx === -1 ? undefined : self.refNameColors[idx % self.refNameColors.length]; }, isValidRefName(refName) { if (!self.refNameAliases) { throw new Error('isValidRefName cannot be called yet, the assembly has not finished loading'); } return !!this.getCanonicalRefName(refName); }, getSeqAdapterRefName(canonicalRefName) { return (self.canonicalToSeqAdapterRefNames?.[canonicalRefName] ?? canonicalRefName); }, })) .actions(self => ({ setLoaded({ regions, refNameAliases, cytobands, }) { this.setRegions(regions); this.setRefNameAliases(refNameAliases); this.setCytobands(cytobands); }, setError(e) { self.error = e; }, setRegions(regions) { self.volatileRegions = regions; }, setRefNameAliases(aliases) { self.refNameAliases = aliases; }, setCytobands(cytobands) { self.cytobands = cytobands; }, setCanonicalToSeqAdapterRefNames(map) { self.canonicalToSeqAdapterRefNames = map; }, setLoadingP(p) { self.loadingP = p; }, load() { if (!self.loadingP) { self.loadingP = this.loadPre().catch((e) => { this.setLoadingP(undefined); this.setError(e); }); } return self.loadingP; }, async loadPre() { const conf = self.configuration; const refNameAliasesAdapterConf = conf?.refNameAliases?.adapter; const cytobandAdapterConf = conf?.cytobands?.adapter; const sequenceAdapterConf = conf?.sequence.adapter; const assemblyName = self.name; const regions = await getAssemblyRegions({ config: sequenceAdapterConf, pluginManager, }); for (const r of regions) { checkRefName(r.refName); } const refNameAliases = {}; const refNameAliasCollection = await getRefNameAliases({ config: refNameAliasesAdapterConf, pluginManager, }); for (const { refName, aliases, override } of refNameAliasCollection) { for (const alias of aliases) { checkRefName(alias); refNameAliases[alias] = refName; } if (override) { refNameAliases[refName] = refName; } } for (const region of regions) { refNameAliases[region.refName] ||= region.refName; } const canonicalToSeqAdapterRefNames = {}; for (const region of regions) { const canonicalName = refNameAliases[region.refName] || region.refName; if (canonicalName !== region.refName) { canonicalToSeqAdapterRefNames[canonicalName] = region.refName; } } this.setCanonicalToSeqAdapterRefNames(canonicalToSeqAdapterRefNames); this.setLoaded({ refNameAliases, regions: regions.map(r => ({ ...r, refName: refNameAliases[r.refName] || r.refName, assemblyName, })), cytobands: await getCytobands({ config: cytobandAdapterConf, pluginManager, }), }); }, })) .views(self => ({ getAdapterMapEntry(adapterConf, options) { const { stopToken, statusCallback, ...rest } = options; if (!options.sessionId) { throw new Error('sessionId is required'); } return adapterLoads.get(adapterConfigCacheKey(adapterConf), { adapterConf, self, options: rest, }, undefined, statusCallback); }, async getRefNameMapForAdapter(adapterConf, opts) { if (!opts.sessionId) { throw new Error('sessionId is required'); } const map = await this.getAdapterMapEntry(adapterConf, opts); return map.forwardMap; }, async getReverseRefNameMapForAdapter(adapterConf, opts) { const map = await this.getAdapterMapEntry(adapterConf, opts); return map.reverseMap; }, afterCreate() { addDisposer(self, autorun(function assemblyRefNamesAutorun() { self.allRefNamesWithLowerCase; }, { name: 'AssemblyRefNames', })); }, })); } async function getRefNameAliases({ config, pluginManager, stopToken, }) { const type = pluginManager.getAdapterType(config.type); const CLASS = await type.getAdapterClass(); const adapter = new CLASS(config, undefined, pluginManager); return adapter.getRefNameAliases({ stopToken }); } async function getCytobands({ config, pluginManager, }) { const type = pluginManager.getAdapterType(config.type); const CLASS = await type.getAdapterClass(); const adapter = new CLASS(config, undefined, pluginManager); return adapter.getData(); } async function getAssemblyRegions({ config, pluginManager, stopToken, }) { const type = pluginManager.getAdapterType(config.type); const CLASS = await type.getAdapterClass(); const adapter = new CLASS(config, undefined, pluginManager); return adapter.getRegions({ stopToken }); }