@jbrowse/core
Version:
JBrowse 2 core libraries used by plugins
406 lines (405 loc) • 15.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const mobx_state_tree_1 = require("mobx-state-tree");
const CorePlugin_1 = __importDefault(require("./CorePlugin"));
const PhasedScheduler_1 = __importDefault(require("./PhasedScheduler"));
const ReExports_1 = __importDefault(require("./ReExports"));
const configuration_1 = require("./configuration");
const AdapterType_1 = __importDefault(require("./pluggableElementTypes/AdapterType"));
const AddTrackWorkflowType_1 = __importDefault(require("./pluggableElementTypes/AddTrackWorkflowType"));
const ConnectionType_1 = __importDefault(require("./pluggableElementTypes/ConnectionType"));
const DisplayType_1 = __importDefault(require("./pluggableElementTypes/DisplayType"));
const InternetAccountType_1 = __importDefault(require("./pluggableElementTypes/InternetAccountType"));
const RpcMethodType_1 = __importDefault(require("./pluggableElementTypes/RpcMethodType"));
const TextSearchAdapterType_1 = __importDefault(require("./pluggableElementTypes/TextSearchAdapterType"));
const TrackType_1 = __importDefault(require("./pluggableElementTypes/TrackType"));
const ViewType_1 = __importDefault(require("./pluggableElementTypes/ViewType"));
const WidgetType_1 = __importDefault(require("./pluggableElementTypes/WidgetType"));
const RendererType_1 = __importDefault(require("./pluggableElementTypes/renderers/RendererType"));
const jexl_1 = __importDefault(require("./util/jexl"));
class TypeRecord {
constructor(typeName, baseClass) {
this.typeName = typeName;
this.baseClass = baseClass;
this.registeredTypes = {};
}
add(name, t) {
this.registeredTypes[name] = t;
}
has(name) {
return name in this.registeredTypes;
}
get(name) {
if (!this.has(name)) {
throw new Error(`${this.typeName} '${name}' not found, perhaps its plugin is not loaded or its plugin has not added it.`);
}
return this.registeredTypes[name];
}
all() {
return Object.values(this.registeredTypes);
}
}
class PluginManager {
constructor(initialPlugins = []) {
this.plugins = [];
this.jexl = (0, jexl_1.default)();
this.pluginMetadata = {};
this.runtimePluginDefinitions = [];
this.elementCreationSchedule = new PhasedScheduler_1.default('renderer', 'adapter', 'text search adapter', 'display', 'track', 'connection', 'view', 'widget', 'rpc method', 'internet account', 'add track workflow');
this.rendererTypes = new TypeRecord('RendererType', RendererType_1.default);
this.adapterTypes = new TypeRecord('AdapterType', AdapterType_1.default);
this.textSearchAdapterTypes = new TypeRecord('TextSearchAdapterType', TextSearchAdapterType_1.default);
this.trackTypes = new TypeRecord('TrackType', TrackType_1.default);
this.displayTypes = new TypeRecord('DisplayType', DisplayType_1.default);
this.connectionTypes = new TypeRecord('ConnectionType', ConnectionType_1.default);
this.viewTypes = new TypeRecord('ViewType', ViewType_1.default);
this.widgetTypes = new TypeRecord('WidgetType', WidgetType_1.default);
this.rpcMethods = new TypeRecord('RpcMethodType', RpcMethodType_1.default);
this.addTrackWidgets = new TypeRecord('AddTrackWorkflow', AddTrackWorkflowType_1.default);
this.internetAccountTypes = new TypeRecord('InternetAccountType', InternetAccountType_1.default);
this.configured = false;
this.extensionPoints = new Map();
this.jbrequireCache = new Map();
this.lib = ReExports_1.default;
this.load = (lib) => {
if (!this.jbrequireCache.has(lib)) {
this.jbrequireCache.set(lib, lib(this));
}
return this.jbrequireCache.get(lib);
};
this.jbrequire = (lib) => {
if (typeof lib === 'string') {
const pack = this.lib[lib];
if (!pack) {
throw new TypeError(`No jbrequire re-export defined for package '${lib}'. If this package must be shared between plugins, add it to ReExports.js. If it does not need to be shared, just import it normally.`);
}
return pack;
}
else if (typeof lib === 'function') {
return this.load(lib);
}
else if (lib.default) {
console.warn('initiated jbrequire on a {default:Function}');
return this.jbrequire(lib.default);
}
throw new TypeError('lib passed to jbrequire must be either a string or a function');
};
this.addPlugin({
plugin: new CorePlugin_1.default(),
metadata: {
isCore: true,
},
});
for (const plugin of initialPlugins) {
this.addPlugin(plugin);
}
}
pluginConfigurationNamespacedSchemas() {
const configurationSchemas = {};
for (const plugin of this.plugins) {
if (plugin.configurationSchema) {
configurationSchemas[plugin.name] = plugin.configurationSchema;
}
}
return configurationSchemas;
}
pluginConfigurationUnnamespacedSchemas() {
let configurationSchemas = {};
for (const plugin of this.plugins) {
if (plugin.configurationSchemaUnnamespaced) {
configurationSchemas = {
...configurationSchemas,
...plugin.configurationSchemaUnnamespaced,
};
}
}
return configurationSchemas;
}
pluginConfigurationRootSchemas() {
let configurationSchemas = {};
for (const plugin of this.plugins) {
if (plugin.rootConfigurationSchema) {
configurationSchemas = {
...configurationSchemas,
...plugin.rootConfigurationSchema(this),
};
}
}
return configurationSchemas;
}
addPlugin(load) {
if (this.configured) {
throw new Error('JBrowse already configured, cannot add plugins');
}
const [plugin, metadata = {}] = 'install' in load && 'configure' in load
? [load, {}]
: [load.plugin, load.metadata];
if (this.plugins.includes(plugin)) {
throw new Error('plugin already installed');
}
this.pluginMetadata[plugin.name] = metadata;
if ('definition' in load) {
this.runtimePluginDefinitions.push(load.definition);
}
plugin.install(this);
this.plugins.push(plugin);
return this;
}
getPlugin(name) {
return this.plugins.find(p => p.name === name);
}
hasPlugin(name) {
return this.getPlugin(name) !== undefined;
}
createPluggableElements() {
if (this.elementCreationSchedule) {
this.elementCreationSchedule.run();
this.elementCreationSchedule = undefined;
}
return this;
}
setRootModel(rootModel) {
this.rootModel = rootModel;
return this;
}
configure() {
if (this.configured) {
throw new Error('already configured');
}
for (const plugin of this.plugins) {
plugin.configure(this);
}
this.configured = true;
return this;
}
getElementTypeRecord(groupName) {
switch (groupName) {
case 'adapter':
return this.adapterTypes;
case 'text search adapter':
return this.textSearchAdapterTypes;
case 'connection':
return this.connectionTypes;
case 'widget':
return this.widgetTypes;
case 'renderer':
return this.rendererTypes;
case 'display':
return this.displayTypes;
case 'track':
return this.trackTypes;
case 'view':
return this.viewTypes;
case 'rpc method':
return this.rpcMethods;
case 'internet account':
return this.internetAccountTypes;
case 'add track workflow':
return this.addTrackWidgets;
default:
throw new Error(`invalid element type '${groupName}'`);
}
}
addElementType(groupName, creationCallback) {
var _a;
if (typeof creationCallback !== 'function') {
throw new Error('must provide a callback function that returns the new type object');
}
const typeRecord = this.getElementTypeRecord(groupName);
(_a = this.elementCreationSchedule) === null || _a === void 0 ? void 0 : _a.add(groupName, () => {
const newElement = creationCallback(this);
if (!newElement.name) {
throw new Error(`cannot add a ${groupName} with no name`);
}
if (typeRecord.has(newElement.name)) {
console.warn(`${groupName} ${newElement.name} already registered, cannot register it again`);
}
else {
typeRecord.add(newElement.name, this.evaluateExtensionPoint('Core-extendPluggableElement', newElement));
}
});
return this;
}
getElementType(groupName, typeName) {
return this.getElementTypeRecord(groupName).get(typeName);
}
getElementTypesInGroup(groupName) {
return this.getElementTypeRecord(groupName).all();
}
getViewElements() {
return this.getElementTypesInGroup('view');
}
getTrackElements() {
return this.getElementTypesInGroup('track');
}
getConnectionElements() {
return this.getElementTypesInGroup('connection');
}
getAddTrackWorkflowElements() {
return this.getElementTypesInGroup('add track workflow');
}
getRpcElements() {
return this.getElementTypesInGroup('rpc method');
}
getDisplayElements() {
return this.getElementTypesInGroup('display');
}
getAdapterElements() {
return this.getElementTypesInGroup('adapter');
}
pluggableMstType(groupName, fieldName, fallback = mobx_state_tree_1.types.maybe(mobx_state_tree_1.types.null)) {
const pluggableTypes = this.getElementTypeRecord(groupName)
.all()
.map(t => t[fieldName])
.filter(t => (0, mobx_state_tree_1.isType)(t) && (0, mobx_state_tree_1.isModelType)(t));
if (pluggableTypes.length === 0 && typeof jest === 'undefined') {
console.warn(`No pluggable types found matching ('${groupName}','${fieldName}')`);
return fallback;
}
return mobx_state_tree_1.types.union(...pluggableTypes);
}
pluggableConfigSchemaType(typeGroup, fieldName = 'configSchema') {
const pluggableTypes = this.getElementTypeRecord(typeGroup)
.all()
.map(t => t[fieldName])
.filter(t => (0, configuration_1.isBareConfigurationSchemaType)(t));
if (pluggableTypes.length === 0) {
pluggableTypes.push((0, configuration_1.ConfigurationSchema)('Null', {}));
}
return mobx_state_tree_1.types.union(...pluggableTypes);
}
getRendererType(typeName) {
return this.rendererTypes.get(typeName);
}
getRendererTypes() {
return this.rendererTypes.all();
}
getAdapterType(typeName) {
return this.adapterTypes.get(typeName);
}
getTextSearchAdapterType(typeName) {
return this.textSearchAdapterTypes.get(typeName);
}
getTrackType(typeName) {
return this.trackTypes.get(typeName);
}
getDisplayType(typeName) {
return this.displayTypes.get(typeName);
}
getViewType(typeName) {
return this.viewTypes.get(typeName);
}
getAddTrackWorkflow(typeName) {
return this.addTrackWidgets.get(typeName);
}
getWidgetType(typeName) {
return this.widgetTypes.get(typeName);
}
getConnectionType(typeName) {
return this.connectionTypes.get(typeName);
}
getRpcMethodType(methodName) {
return this.rpcMethods.get(methodName);
}
getInternetAccountType(name) {
return this.internetAccountTypes.get(name);
}
addRendererType(cb) {
return this.addElementType('renderer', cb);
}
addAdapterType(cb) {
return this.addElementType('adapter', cb);
}
addTextSearchAdapterType(cb) {
return this.addElementType('text search adapter', cb);
}
addTrackType(cb) {
const callback = () => {
const track = cb(this);
const displays = this.getElementTypesInGroup('display');
for (const display of displays) {
if (display.trackType === track.name &&
!track.displayTypes.includes(display)) {
track.addDisplayType(display);
}
}
return track;
};
return this.addElementType('track', callback);
}
addDisplayType(cb) {
return this.addElementType('display', cb);
}
addViewType(cb) {
const callback = () => {
const newView = cb(this);
const displays = this.getElementTypesInGroup('display');
for (const display of displays) {
if ((display.viewType === newView.name ||
display.viewType === newView.extendedName) &&
!newView.displayTypes.includes(display)) {
newView.addDisplayType(display);
}
}
return newView;
};
return this.addElementType('view', callback);
}
addWidgetType(cb) {
return this.addElementType('widget', cb);
}
addConnectionType(cb) {
return this.addElementType('connection', cb);
}
addRpcMethod(cb) {
return this.addElementType('rpc method', cb);
}
addInternetAccountType(cb) {
return this.addElementType('internet account', cb);
}
addAddTrackWorkflowType(cb) {
return this.addElementType('add track workflow', cb);
}
addToExtensionPoint(extensionPointName, callback) {
let callbacks = this.extensionPoints.get(extensionPointName);
if (!callbacks) {
callbacks = [];
this.extensionPoints.set(extensionPointName, callbacks);
}
callbacks.push(callback);
}
evaluateExtensionPoint(extensionPointName, extendee, props) {
const callbacks = this.extensionPoints.get(extensionPointName);
let accumulator = extendee;
if (callbacks) {
for (const callback of callbacks) {
try {
accumulator = callback(accumulator, props);
}
catch (error) {
console.error(error);
}
}
}
return accumulator;
}
async evaluateAsyncExtensionPoint(extensionPointName, extendee, props) {
const callbacks = this.extensionPoints.get(extensionPointName);
let accumulator = extendee;
if (callbacks) {
for (const callback of callbacks) {
try {
accumulator = await callback(accumulator, props);
}
catch (error) {
console.error(error);
}
}
}
return accumulator;
}
}
exports.default = PluginManager;