@jbrowse/core
Version:
JBrowse 2 core libraries used by plugins
329 lines (328 loc) • 11.9 kB
JavaScript
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 });
}