@lumino/coreutils
Advanced tools
Comparing version 2.1.2 to 2.2.0
@@ -0,1 +1,3 @@ | ||
import { topologicSort } from '@lumino/algorithm'; | ||
// Copyright (c) Jupyter Development Team. | ||
@@ -282,2 +284,523 @@ // Distributed under the terms of the Modified BSD License. | ||
// Distributed under the terms of the Modified BSD License. | ||
/** | ||
* Plugin registry. | ||
*/ | ||
class PluginRegistry { | ||
constructor(options = {}) { | ||
this._application = null; | ||
this._validatePlugin = () => true; | ||
this._plugins = new Map(); | ||
this._services = new Map(); | ||
if (options.validatePlugin) { | ||
console.info('Plugins may be rejected by the custom validation plugin method.'); | ||
this._validatePlugin = options.validatePlugin; | ||
} | ||
} | ||
/** | ||
* The application object. | ||
* | ||
* It will be provided as first argument to the | ||
* plugins activation and deactivation functions. | ||
* | ||
* It can only be set once. | ||
* | ||
* By default, it is `null`. | ||
*/ | ||
get application() { | ||
return this._application; | ||
} | ||
set application(v) { | ||
if (this._application !== null) { | ||
throw Error('PluginRegistry.application is already set. It cannot be overridden.'); | ||
} | ||
this._application = v; | ||
} | ||
/** | ||
* The list of all the deferred plugins. | ||
*/ | ||
get deferredPlugins() { | ||
return Array.from(this._plugins) | ||
.filter(([id, plugin]) => plugin.autoStart === 'defer') | ||
.map(([id, plugin]) => id); | ||
} | ||
/** | ||
* Get a plugin description. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns The plugin description. | ||
*/ | ||
getPluginDescription(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : ''; | ||
} | ||
/** | ||
* Test whether a plugin is registered with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is registered, `false` otherwise. | ||
*/ | ||
hasPlugin(id) { | ||
return this._plugins.has(id); | ||
} | ||
/** | ||
* Test whether a plugin is activated with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is activated, `false` otherwise. | ||
*/ | ||
isPluginActivated(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.activated) !== null && _b !== void 0 ? _b : false; | ||
} | ||
/** | ||
* List the IDs of the plugins registered with the application. | ||
* | ||
* @returns A new array of the registered plugin IDs. | ||
*/ | ||
listPlugins() { | ||
return Array.from(this._plugins.keys()); | ||
} | ||
/** | ||
* Register a plugin with the application. | ||
* | ||
* @param plugin - The plugin to register. | ||
* | ||
* #### Notes | ||
* An error will be thrown if a plugin with the same ID is already | ||
* registered, or if the plugin has a circular dependency. | ||
* | ||
* If the plugin provides a service which has already been provided | ||
* by another plugin, the new service will override the old service. | ||
*/ | ||
registerPlugin(plugin) { | ||
// Throw an error if the plugin ID is already registered. | ||
if (this._plugins.has(plugin.id)) { | ||
throw new TypeError(`Plugin '${plugin.id}' is already registered.`); | ||
} | ||
if (!this._validatePlugin(plugin)) { | ||
throw new Error(`Plugin '${plugin.id}' is not valid.`); | ||
} | ||
// Create the normalized plugin data. | ||
const data = Private.createPluginData(plugin); | ||
// Ensure the plugin does not cause a cyclic dependency. | ||
Private.ensureNoCycle(data, this._plugins, this._services); | ||
// Add the service token to the service map. | ||
if (data.provides) { | ||
this._services.set(data.provides, data.id); | ||
} | ||
// Add the plugin to the plugin map. | ||
this._plugins.set(data.id, data); | ||
} | ||
/** | ||
* Register multiple plugins with the application. | ||
* | ||
* @param plugins - The plugins to register. | ||
* | ||
* #### Notes | ||
* This calls `registerPlugin()` for each of the given plugins. | ||
*/ | ||
registerPlugins(plugins) { | ||
for (const plugin of plugins) { | ||
this.registerPlugin(plugin); | ||
} | ||
} | ||
/** | ||
* Deregister a plugin with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param force - Whether to deregister the plugin even if it is active. | ||
*/ | ||
deregisterPlugin(id, force) { | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
return; | ||
} | ||
if (plugin.activated && !force) { | ||
throw new Error(`Plugin '${id}' is still active.`); | ||
} | ||
this._plugins.delete(id); | ||
} | ||
/** | ||
* Activate the plugin with the given ID. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A promise which resolves when the plugin is activated | ||
* or rejects with an error if it cannot be activated. | ||
*/ | ||
async activatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Resolve immediately if the plugin is already activated. | ||
if (plugin.activated) { | ||
return; | ||
} | ||
// Return the pending resolver promise if it exists. | ||
if (plugin.promise) { | ||
return plugin.promise; | ||
} | ||
// Resolve the required services for the plugin. | ||
const required = plugin.requires.map(t => this.resolveRequiredService(t)); | ||
// Resolve the optional services for the plugin. | ||
const optional = plugin.optional.map(t => this.resolveOptionalService(t)); | ||
// Setup the resolver promise for the plugin. | ||
plugin.promise = Promise.all([...required, ...optional]) | ||
.then(services => plugin.activate.apply(undefined, [this.application, ...services])) | ||
.then(service => { | ||
plugin.service = service; | ||
plugin.activated = true; | ||
plugin.promise = null; | ||
}) | ||
.catch(error => { | ||
plugin.promise = null; | ||
throw error; | ||
}); | ||
// Return the pending resolver promise. | ||
return plugin.promise; | ||
} | ||
/** | ||
* Activate all the deferred plugins. | ||
* | ||
* @returns A promise which will resolve when each plugin is activated | ||
* or rejects with an error if one cannot be activated. | ||
*/ | ||
async activatePlugins(kind, options = {}) { | ||
switch (kind) { | ||
case 'defer': { | ||
const promises = this.deferredPlugins | ||
.filter(pluginId => this._plugins.get(pluginId).autoStart) | ||
.map(pluginId => { | ||
return this.activatePlugin(pluginId); | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
case 'startUp': { | ||
// Collect the ids of the startup plugins. | ||
const startups = Private.collectStartupPlugins(this._plugins, options); | ||
// Generate the activation promises. | ||
const promises = startups.map(async (id) => { | ||
try { | ||
return await this.activatePlugin(id); | ||
} | ||
catch (error) { | ||
console.error(`Plugin '${id}' failed to activate.`, error); | ||
} | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* Deactivate the plugin and its downstream dependents if and only if the | ||
* plugin and its dependents all support `deactivate`. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A list of IDs of downstream plugins deactivated with this one. | ||
*/ | ||
async deactivatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Bail early if the plugin is not activated. | ||
if (!plugin.activated) { | ||
return []; | ||
} | ||
// Check that this plugin can deactivate. | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin '${id}'#deactivate() method missing`); | ||
} | ||
// Find the optimal deactivation order for plugins downstream of this one. | ||
const manifest = Private.findDependents(id, this._plugins, this._services); | ||
const downstream = manifest.map(id => this._plugins.get(id)); | ||
// Check that all downstream plugins can deactivate. | ||
for (const plugin of downstream) { | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin ${plugin.id}#deactivate() method missing (depends on ${id})`); | ||
} | ||
} | ||
// Deactivate all downstream plugins. | ||
for (const plugin of downstream) { | ||
const services = [...plugin.requires, ...plugin.optional].map(service => { | ||
const id = this._services.get(service); | ||
return id ? this._plugins.get(id).service : null; | ||
}); | ||
// Await deactivation so the next plugins only receive active services. | ||
await plugin.deactivate(this.application, ...services); | ||
plugin.service = null; | ||
plugin.activated = false; | ||
} | ||
// Remove plugin ID and return manifest of deactivated plugins. | ||
manifest.pop(); | ||
return manifest; | ||
} | ||
/** | ||
* Resolve a required service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or rejects with an error if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the required services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveRequiredService(token) { | ||
// Reject the promise if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
throw new TypeError(`No provider for: ${token.name}.`); | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
await this.activatePlugin(id); | ||
} | ||
return plugin.service; | ||
} | ||
/** | ||
* Resolve an optional service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or `null` if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the optional services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveOptionalService(token) { | ||
// Resolve with `null` if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
return null; | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
try { | ||
await this.activatePlugin(id); | ||
} | ||
catch (reason) { | ||
console.error(reason); | ||
return null; | ||
} | ||
} | ||
return plugin.service; | ||
} | ||
} | ||
/** | ||
* The namespace for the module implementation details. | ||
*/ | ||
var Private; | ||
(function (Private) { | ||
class PluginData { | ||
constructor(plugin) { | ||
var _a, _b, _c, _d; | ||
this._activated = false; | ||
this._promise = null; | ||
this._service = null; | ||
this.id = plugin.id; | ||
this.description = (_a = plugin.description) !== null && _a !== void 0 ? _a : ''; | ||
this.activate = plugin.activate; | ||
this.deactivate = (_b = plugin.deactivate) !== null && _b !== void 0 ? _b : null; | ||
this.provides = (_c = plugin.provides) !== null && _c !== void 0 ? _c : null; | ||
this.autoStart = (_d = plugin.autoStart) !== null && _d !== void 0 ? _d : false; | ||
this.requires = plugin.requires ? plugin.requires.slice() : []; | ||
this.optional = plugin.optional ? plugin.optional.slice() : []; | ||
} | ||
/** | ||
* Whether the plugin has been activated. | ||
*/ | ||
get activated() { | ||
return this._activated; | ||
} | ||
set activated(a) { | ||
this._activated = a; | ||
} | ||
/** | ||
* The resolved service for the plugin, or `null`. | ||
*/ | ||
get service() { | ||
return this._service; | ||
} | ||
set service(s) { | ||
this._service = s; | ||
} | ||
/** | ||
* The pending resolver promise, or `null`. | ||
*/ | ||
get promise() { | ||
return this._promise; | ||
} | ||
set promise(p) { | ||
this._promise = p; | ||
} | ||
} | ||
/** | ||
* Create a normalized plugin data object for the given plugin. | ||
*/ | ||
function createPluginData(plugin) { | ||
return new PluginData(plugin); | ||
} | ||
Private.createPluginData = createPluginData; | ||
/** | ||
* Ensure no cycle is present in the plugin resolution graph. | ||
* | ||
* If a cycle is detected, an error will be thrown. | ||
*/ | ||
function ensureNoCycle(plugin, plugins, services) { | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
const visit = (token) => { | ||
if (token === plugin.provides) { | ||
return true; | ||
} | ||
const id = services.get(token); | ||
if (!id) { | ||
return false; | ||
} | ||
const visited = plugins.get(id); | ||
const dependencies = [...visited.requires, ...visited.optional]; | ||
if (dependencies.length === 0) { | ||
return false; | ||
} | ||
trace.push(id); | ||
if (dependencies.some(visit)) { | ||
return true; | ||
} | ||
trace.pop(); | ||
return false; | ||
}; | ||
// Bail early if there cannot be a cycle. | ||
if (!plugin.provides || dependencies.length === 0) { | ||
return; | ||
} | ||
// Setup a stack to trace service resolution. | ||
const trace = [plugin.id]; | ||
// Throw an exception if a cycle is present. | ||
if (dependencies.some(visit)) { | ||
throw new ReferenceError(`Cycle detected: ${trace.join(' -> ')}.`); | ||
} | ||
} | ||
Private.ensureNoCycle = ensureNoCycle; | ||
/** | ||
* Find dependents in deactivation order. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param plugins - The map containing all plugins. | ||
* | ||
* @param services - The map containing all services. | ||
* | ||
* @returns A list of dependent plugin IDs in order of deactivation | ||
* | ||
* #### Notes | ||
* The final item of the returned list is always the plugin of interest. | ||
*/ | ||
function findDependents(id, plugins, services) { | ||
const edges = new Array(); | ||
const add = (id) => { | ||
const plugin = plugins.get(id); | ||
// FIXME In the case of missing optional dependencies, we may consider | ||
// deactivating and reactivating the plugin without the missing service. | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
edges.push(...dependencies.reduce((acc, dep) => { | ||
const service = services.get(dep); | ||
if (service) { | ||
// An edge is oriented from dependent to provider. | ||
acc.push([id, service]); | ||
} | ||
return acc; | ||
}, [])); | ||
}; | ||
for (const id of plugins.keys()) { | ||
add(id); | ||
} | ||
// Filter edges | ||
// - Get all packages that dependent on the package to be deactivated | ||
const newEdges = edges.filter(edge => edge[1] === id); | ||
let oldSize = 0; | ||
while (newEdges.length > oldSize) { | ||
const previousSize = newEdges.length; | ||
// Get all packages that dependent on packages that will be deactivated | ||
const packagesOfInterest = new Set(newEdges.map(edge => edge[0])); | ||
for (const poi of packagesOfInterest) { | ||
edges | ||
.filter(edge => edge[1] === poi) | ||
.forEach(edge => { | ||
// We check it is not already included to deal with circular dependencies | ||
if (!newEdges.includes(edge)) { | ||
newEdges.push(edge); | ||
} | ||
}); | ||
} | ||
oldSize = previousSize; | ||
} | ||
const sorted = topologicSort(newEdges); | ||
const index = sorted.findIndex(candidate => candidate === id); | ||
if (index === -1) { | ||
return [id]; | ||
} | ||
return sorted.slice(0, index + 1); | ||
} | ||
Private.findDependents = findDependents; | ||
/** | ||
* Collect the IDs of the plugins to activate on startup. | ||
*/ | ||
function collectStartupPlugins(plugins, options) { | ||
// Create a set to hold the plugin IDs. | ||
const collection = new Set(); | ||
// Collect the auto-start (non deferred) plugins. | ||
for (const id of plugins.keys()) { | ||
if (plugins.get(id).autoStart === true) { | ||
collection.add(id); | ||
} | ||
} | ||
// Add the startup plugins. | ||
if (options.startPlugins) { | ||
for (const id of options.startPlugins) { | ||
collection.add(id); | ||
} | ||
} | ||
// Remove the ignored plugins. | ||
if (options.ignorePlugins) { | ||
for (const id of options.ignorePlugins) { | ||
collection.delete(id); | ||
} | ||
} | ||
// Return the collected startup plugins. | ||
return Array.from(collection); | ||
} | ||
Private.collectStartupPlugins = collectStartupPlugins; | ||
})(Private || (Private = {})); | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
/*----------------------------------------------------------------------------- | ||
@@ -519,3 +1042,3 @@ | Copyright (c) 2014-2017, PhosphorJS Contributors | ||
export { JSONExt, MimeData, PromiseDelegate, Random, Token, UUID }; | ||
export { JSONExt, MimeData, PluginRegistry, PromiseDelegate, Random, Token, UUID }; | ||
//# sourceMappingURL=index.es6.js.map |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.lumino_coreutils = {})); | ||
})(this, (function (exports) { 'use strict'; | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@lumino/algorithm')) : | ||
typeof define === 'function' && define.amd ? define(['exports', '@lumino/algorithm'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.lumino_coreutils = {}, global.lumino_algorithm)); | ||
})(this, (function (exports, algorithm) { 'use strict'; | ||
@@ -288,2 +288,523 @@ // Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
/** | ||
* Plugin registry. | ||
*/ | ||
class PluginRegistry { | ||
constructor(options = {}) { | ||
this._application = null; | ||
this._validatePlugin = () => true; | ||
this._plugins = new Map(); | ||
this._services = new Map(); | ||
if (options.validatePlugin) { | ||
console.info('Plugins may be rejected by the custom validation plugin method.'); | ||
this._validatePlugin = options.validatePlugin; | ||
} | ||
} | ||
/** | ||
* The application object. | ||
* | ||
* It will be provided as first argument to the | ||
* plugins activation and deactivation functions. | ||
* | ||
* It can only be set once. | ||
* | ||
* By default, it is `null`. | ||
*/ | ||
get application() { | ||
return this._application; | ||
} | ||
set application(v) { | ||
if (this._application !== null) { | ||
throw Error('PluginRegistry.application is already set. It cannot be overridden.'); | ||
} | ||
this._application = v; | ||
} | ||
/** | ||
* The list of all the deferred plugins. | ||
*/ | ||
get deferredPlugins() { | ||
return Array.from(this._plugins) | ||
.filter(([id, plugin]) => plugin.autoStart === 'defer') | ||
.map(([id, plugin]) => id); | ||
} | ||
/** | ||
* Get a plugin description. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns The plugin description. | ||
*/ | ||
getPluginDescription(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : ''; | ||
} | ||
/** | ||
* Test whether a plugin is registered with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is registered, `false` otherwise. | ||
*/ | ||
hasPlugin(id) { | ||
return this._plugins.has(id); | ||
} | ||
/** | ||
* Test whether a plugin is activated with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is activated, `false` otherwise. | ||
*/ | ||
isPluginActivated(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.activated) !== null && _b !== void 0 ? _b : false; | ||
} | ||
/** | ||
* List the IDs of the plugins registered with the application. | ||
* | ||
* @returns A new array of the registered plugin IDs. | ||
*/ | ||
listPlugins() { | ||
return Array.from(this._plugins.keys()); | ||
} | ||
/** | ||
* Register a plugin with the application. | ||
* | ||
* @param plugin - The plugin to register. | ||
* | ||
* #### Notes | ||
* An error will be thrown if a plugin with the same ID is already | ||
* registered, or if the plugin has a circular dependency. | ||
* | ||
* If the plugin provides a service which has already been provided | ||
* by another plugin, the new service will override the old service. | ||
*/ | ||
registerPlugin(plugin) { | ||
// Throw an error if the plugin ID is already registered. | ||
if (this._plugins.has(plugin.id)) { | ||
throw new TypeError(`Plugin '${plugin.id}' is already registered.`); | ||
} | ||
if (!this._validatePlugin(plugin)) { | ||
throw new Error(`Plugin '${plugin.id}' is not valid.`); | ||
} | ||
// Create the normalized plugin data. | ||
const data = Private.createPluginData(plugin); | ||
// Ensure the plugin does not cause a cyclic dependency. | ||
Private.ensureNoCycle(data, this._plugins, this._services); | ||
// Add the service token to the service map. | ||
if (data.provides) { | ||
this._services.set(data.provides, data.id); | ||
} | ||
// Add the plugin to the plugin map. | ||
this._plugins.set(data.id, data); | ||
} | ||
/** | ||
* Register multiple plugins with the application. | ||
* | ||
* @param plugins - The plugins to register. | ||
* | ||
* #### Notes | ||
* This calls `registerPlugin()` for each of the given plugins. | ||
*/ | ||
registerPlugins(plugins) { | ||
for (const plugin of plugins) { | ||
this.registerPlugin(plugin); | ||
} | ||
} | ||
/** | ||
* Deregister a plugin with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param force - Whether to deregister the plugin even if it is active. | ||
*/ | ||
deregisterPlugin(id, force) { | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
return; | ||
} | ||
if (plugin.activated && !force) { | ||
throw new Error(`Plugin '${id}' is still active.`); | ||
} | ||
this._plugins.delete(id); | ||
} | ||
/** | ||
* Activate the plugin with the given ID. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A promise which resolves when the plugin is activated | ||
* or rejects with an error if it cannot be activated. | ||
*/ | ||
async activatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Resolve immediately if the plugin is already activated. | ||
if (plugin.activated) { | ||
return; | ||
} | ||
// Return the pending resolver promise if it exists. | ||
if (plugin.promise) { | ||
return plugin.promise; | ||
} | ||
// Resolve the required services for the plugin. | ||
const required = plugin.requires.map(t => this.resolveRequiredService(t)); | ||
// Resolve the optional services for the plugin. | ||
const optional = plugin.optional.map(t => this.resolveOptionalService(t)); | ||
// Setup the resolver promise for the plugin. | ||
plugin.promise = Promise.all([...required, ...optional]) | ||
.then(services => plugin.activate.apply(undefined, [this.application, ...services])) | ||
.then(service => { | ||
plugin.service = service; | ||
plugin.activated = true; | ||
plugin.promise = null; | ||
}) | ||
.catch(error => { | ||
plugin.promise = null; | ||
throw error; | ||
}); | ||
// Return the pending resolver promise. | ||
return plugin.promise; | ||
} | ||
/** | ||
* Activate all the deferred plugins. | ||
* | ||
* @returns A promise which will resolve when each plugin is activated | ||
* or rejects with an error if one cannot be activated. | ||
*/ | ||
async activatePlugins(kind, options = {}) { | ||
switch (kind) { | ||
case 'defer': { | ||
const promises = this.deferredPlugins | ||
.filter(pluginId => this._plugins.get(pluginId).autoStart) | ||
.map(pluginId => { | ||
return this.activatePlugin(pluginId); | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
case 'startUp': { | ||
// Collect the ids of the startup plugins. | ||
const startups = Private.collectStartupPlugins(this._plugins, options); | ||
// Generate the activation promises. | ||
const promises = startups.map(async (id) => { | ||
try { | ||
return await this.activatePlugin(id); | ||
} | ||
catch (error) { | ||
console.error(`Plugin '${id}' failed to activate.`, error); | ||
} | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* Deactivate the plugin and its downstream dependents if and only if the | ||
* plugin and its dependents all support `deactivate`. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A list of IDs of downstream plugins deactivated with this one. | ||
*/ | ||
async deactivatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Bail early if the plugin is not activated. | ||
if (!plugin.activated) { | ||
return []; | ||
} | ||
// Check that this plugin can deactivate. | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin '${id}'#deactivate() method missing`); | ||
} | ||
// Find the optimal deactivation order for plugins downstream of this one. | ||
const manifest = Private.findDependents(id, this._plugins, this._services); | ||
const downstream = manifest.map(id => this._plugins.get(id)); | ||
// Check that all downstream plugins can deactivate. | ||
for (const plugin of downstream) { | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin ${plugin.id}#deactivate() method missing (depends on ${id})`); | ||
} | ||
} | ||
// Deactivate all downstream plugins. | ||
for (const plugin of downstream) { | ||
const services = [...plugin.requires, ...plugin.optional].map(service => { | ||
const id = this._services.get(service); | ||
return id ? this._plugins.get(id).service : null; | ||
}); | ||
// Await deactivation so the next plugins only receive active services. | ||
await plugin.deactivate(this.application, ...services); | ||
plugin.service = null; | ||
plugin.activated = false; | ||
} | ||
// Remove plugin ID and return manifest of deactivated plugins. | ||
manifest.pop(); | ||
return manifest; | ||
} | ||
/** | ||
* Resolve a required service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or rejects with an error if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the required services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveRequiredService(token) { | ||
// Reject the promise if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
throw new TypeError(`No provider for: ${token.name}.`); | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
await this.activatePlugin(id); | ||
} | ||
return plugin.service; | ||
} | ||
/** | ||
* Resolve an optional service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or `null` if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the optional services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveOptionalService(token) { | ||
// Resolve with `null` if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
return null; | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
try { | ||
await this.activatePlugin(id); | ||
} | ||
catch (reason) { | ||
console.error(reason); | ||
return null; | ||
} | ||
} | ||
return plugin.service; | ||
} | ||
} | ||
/** | ||
* The namespace for the module implementation details. | ||
*/ | ||
var Private; | ||
(function (Private) { | ||
class PluginData { | ||
constructor(plugin) { | ||
var _a, _b, _c, _d; | ||
this._activated = false; | ||
this._promise = null; | ||
this._service = null; | ||
this.id = plugin.id; | ||
this.description = (_a = plugin.description) !== null && _a !== void 0 ? _a : ''; | ||
this.activate = plugin.activate; | ||
this.deactivate = (_b = plugin.deactivate) !== null && _b !== void 0 ? _b : null; | ||
this.provides = (_c = plugin.provides) !== null && _c !== void 0 ? _c : null; | ||
this.autoStart = (_d = plugin.autoStart) !== null && _d !== void 0 ? _d : false; | ||
this.requires = plugin.requires ? plugin.requires.slice() : []; | ||
this.optional = plugin.optional ? plugin.optional.slice() : []; | ||
} | ||
/** | ||
* Whether the plugin has been activated. | ||
*/ | ||
get activated() { | ||
return this._activated; | ||
} | ||
set activated(a) { | ||
this._activated = a; | ||
} | ||
/** | ||
* The resolved service for the plugin, or `null`. | ||
*/ | ||
get service() { | ||
return this._service; | ||
} | ||
set service(s) { | ||
this._service = s; | ||
} | ||
/** | ||
* The pending resolver promise, or `null`. | ||
*/ | ||
get promise() { | ||
return this._promise; | ||
} | ||
set promise(p) { | ||
this._promise = p; | ||
} | ||
} | ||
/** | ||
* Create a normalized plugin data object for the given plugin. | ||
*/ | ||
function createPluginData(plugin) { | ||
return new PluginData(plugin); | ||
} | ||
Private.createPluginData = createPluginData; | ||
/** | ||
* Ensure no cycle is present in the plugin resolution graph. | ||
* | ||
* If a cycle is detected, an error will be thrown. | ||
*/ | ||
function ensureNoCycle(plugin, plugins, services) { | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
const visit = (token) => { | ||
if (token === plugin.provides) { | ||
return true; | ||
} | ||
const id = services.get(token); | ||
if (!id) { | ||
return false; | ||
} | ||
const visited = plugins.get(id); | ||
const dependencies = [...visited.requires, ...visited.optional]; | ||
if (dependencies.length === 0) { | ||
return false; | ||
} | ||
trace.push(id); | ||
if (dependencies.some(visit)) { | ||
return true; | ||
} | ||
trace.pop(); | ||
return false; | ||
}; | ||
// Bail early if there cannot be a cycle. | ||
if (!plugin.provides || dependencies.length === 0) { | ||
return; | ||
} | ||
// Setup a stack to trace service resolution. | ||
const trace = [plugin.id]; | ||
// Throw an exception if a cycle is present. | ||
if (dependencies.some(visit)) { | ||
throw new ReferenceError(`Cycle detected: ${trace.join(' -> ')}.`); | ||
} | ||
} | ||
Private.ensureNoCycle = ensureNoCycle; | ||
/** | ||
* Find dependents in deactivation order. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param plugins - The map containing all plugins. | ||
* | ||
* @param services - The map containing all services. | ||
* | ||
* @returns A list of dependent plugin IDs in order of deactivation | ||
* | ||
* #### Notes | ||
* The final item of the returned list is always the plugin of interest. | ||
*/ | ||
function findDependents(id, plugins, services) { | ||
const edges = new Array(); | ||
const add = (id) => { | ||
const plugin = plugins.get(id); | ||
// FIXME In the case of missing optional dependencies, we may consider | ||
// deactivating and reactivating the plugin without the missing service. | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
edges.push(...dependencies.reduce((acc, dep) => { | ||
const service = services.get(dep); | ||
if (service) { | ||
// An edge is oriented from dependent to provider. | ||
acc.push([id, service]); | ||
} | ||
return acc; | ||
}, [])); | ||
}; | ||
for (const id of plugins.keys()) { | ||
add(id); | ||
} | ||
// Filter edges | ||
// - Get all packages that dependent on the package to be deactivated | ||
const newEdges = edges.filter(edge => edge[1] === id); | ||
let oldSize = 0; | ||
while (newEdges.length > oldSize) { | ||
const previousSize = newEdges.length; | ||
// Get all packages that dependent on packages that will be deactivated | ||
const packagesOfInterest = new Set(newEdges.map(edge => edge[0])); | ||
for (const poi of packagesOfInterest) { | ||
edges | ||
.filter(edge => edge[1] === poi) | ||
.forEach(edge => { | ||
// We check it is not already included to deal with circular dependencies | ||
if (!newEdges.includes(edge)) { | ||
newEdges.push(edge); | ||
} | ||
}); | ||
} | ||
oldSize = previousSize; | ||
} | ||
const sorted = algorithm.topologicSort(newEdges); | ||
const index = sorted.findIndex(candidate => candidate === id); | ||
if (index === -1) { | ||
return [id]; | ||
} | ||
return sorted.slice(0, index + 1); | ||
} | ||
Private.findDependents = findDependents; | ||
/** | ||
* Collect the IDs of the plugins to activate on startup. | ||
*/ | ||
function collectStartupPlugins(plugins, options) { | ||
// Create a set to hold the plugin IDs. | ||
const collection = new Set(); | ||
// Collect the auto-start (non deferred) plugins. | ||
for (const id of plugins.keys()) { | ||
if (plugins.get(id).autoStart === true) { | ||
collection.add(id); | ||
} | ||
} | ||
// Add the startup plugins. | ||
if (options.startPlugins) { | ||
for (const id of options.startPlugins) { | ||
collection.add(id); | ||
} | ||
} | ||
// Remove the ignored plugins. | ||
if (options.ignorePlugins) { | ||
for (const id of options.ignorePlugins) { | ||
collection.delete(id); | ||
} | ||
} | ||
// Return the collected startup plugins. | ||
return Array.from(collection); | ||
} | ||
Private.collectStartupPlugins = collectStartupPlugins; | ||
})(Private || (Private = {})); | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
/*----------------------------------------------------------------------------- | ||
@@ -526,2 +1047,3 @@ | Copyright (c) 2014-2017, PhosphorJS Contributors | ||
exports.MimeData = MimeData; | ||
exports.PluginRegistry = PluginRegistry; | ||
exports.PromiseDelegate = PromiseDelegate; | ||
@@ -528,0 +1050,0 @@ exports.Token = Token; |
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).lumino_coreutils={})}(this,(function(t){"use strict";t.JSONExt=void 0,function(t){function e(t){return null===t||"boolean"==typeof t||"number"==typeof t||"string"==typeof t}function n(t){return Array.isArray(t)}function r(t,i){if(t===i)return!0;if(e(t)||e(i))return!1;let o=n(t),s=n(i);return o===s&&(o&&s?function(t,e){if(t===e)return!0;if(t.length!==e.length)return!1;for(let n=0,i=t.length;n<i;++n)if(!r(t[n],e[n]))return!1;return!0}(t,i):function(t,e){if(t===e)return!0;for(let n in t)if(void 0!==t[n]&&!(n in e))return!1;for(let n in e)if(void 0!==e[n]&&!(n in t))return!1;for(let n in t){let i=t[n],o=e[n];if(void 0!==i||void 0!==o){if(void 0===i||void 0===o)return!1;if(!r(i,o))return!1}}return!0}(t,i))}function i(t){return e(t)?t:n(t)?function(t){let e=new Array(t.length);for(let n=0,r=t.length;n<r;++n)e[n]=i(t[n]);return e}(t):function(t){let e={};for(let n in t){let r=t[n];void 0!==r&&(e[n]=i(r))}return e}(t)}t.emptyObject=Object.freeze({}),t.emptyArray=Object.freeze([]),t.isPrimitive=e,t.isArray=n,t.isObject=function(t){return!e(t)&&!n(t)},t.deepEqual=r,t.deepCopy=i}(t.JSONExt||(t.JSONExt={}));function e(t){let e=0;for(let n=0,r=t.length;n<r;++n)n%4==0&&(e=4294967295*Math.random()>>>0),t[n]=255&e,e>>>=8}t.Random=void 0,(t.Random||(t.Random={})).getRandomValues=(()=>{const t="undefined"!=typeof window&&(window.crypto||window.msCrypto)||null;return t&&"function"==typeof t.getRandomValues?function(e){return t.getRandomValues(e)}:e})(),t.UUID=void 0,(t.UUID||(t.UUID={})).uuid4=function(t){const e=new Uint8Array(16),n=new Array(256);for(let t=0;t<16;++t)n[t]="0"+t.toString(16);for(let t=16;t<256;++t)n[t]=t.toString(16);return function(){return t(e),e[6]=64|15&e[6],e[8]=128|63&e[8],n[e[0]]+n[e[1]]+n[e[2]]+n[e[3]]+"-"+n[e[4]]+n[e[5]]+"-"+n[e[6]]+n[e[7]]+"-"+n[e[8]]+n[e[9]]+"-"+n[e[10]]+n[e[11]]+n[e[12]]+n[e[13]]+n[e[14]]+n[e[15]]}}(t.Random.getRandomValues),t.MimeData=class{constructor(){this._types=[],this._values=[]}types(){return this._types.slice()}hasData(t){return-1!==this._types.indexOf(t)}getData(t){let e=this._types.indexOf(t);return-1!==e?this._values[e]:void 0}setData(t,e){this.clearData(t),this._types.push(t),this._values.push(e)}clearData(t){let e=this._types.indexOf(t);-1!==e&&(this._types.splice(e,1),this._values.splice(e,1))}clear(){this._types.length=0,this._values.length=0}},t.PromiseDelegate=class{constructor(){this.promise=new Promise(((t,e)=>{this._resolve=t,this._reject=e}))}resolve(t){(0,this._resolve)(t)}reject(t){(0,this._reject)(t)}},t.Token=class{constructor(t,e){this.name=t,this.description=null!=e?e:"",this._tokenStructuralPropertyT=null}}})); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@lumino/algorithm")):"function"==typeof define&&define.amd?define(["exports","@lumino/algorithm"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).lumino_coreutils={},t.lumino_algorithm)}(this,(function(t,e){"use strict";t.JSONExt=void 0,function(t){function e(t){return null===t||"boolean"==typeof t||"number"==typeof t||"string"==typeof t}function i(t){return Array.isArray(t)}function r(t,n){if(t===n)return!0;if(e(t)||e(n))return!1;let s=i(t),o=i(n);return s===o&&(s&&o?function(t,e){if(t===e)return!0;if(t.length!==e.length)return!1;for(let i=0,n=t.length;i<n;++i)if(!r(t[i],e[i]))return!1;return!0}(t,n):function(t,e){if(t===e)return!0;for(let i in t)if(void 0!==t[i]&&!(i in e))return!1;for(let i in e)if(void 0!==e[i]&&!(i in t))return!1;for(let i in t){let n=t[i],s=e[i];if(void 0!==n||void 0!==s){if(void 0===n||void 0===s)return!1;if(!r(n,s))return!1}}return!0}(t,n))}function n(t){return e(t)?t:i(t)?function(t){let e=new Array(t.length);for(let i=0,r=t.length;i<r;++i)e[i]=n(t[i]);return e}(t):function(t){let e={};for(let i in t){let r=t[i];void 0!==r&&(e[i]=n(r))}return e}(t)}t.emptyObject=Object.freeze({}),t.emptyArray=Object.freeze([]),t.isPrimitive=e,t.isArray=i,t.isObject=function(t){return!e(t)&&!i(t)},t.deepEqual=r,t.deepCopy=n}(t.JSONExt||(t.JSONExt={}));var i;!function(t){class i{constructor(t){var e,i,r,n;this._activated=!1,this._promise=null,this._service=null,this.id=t.id,this.description=null!==(e=t.description)&&void 0!==e?e:"",this.activate=t.activate,this.deactivate=null!==(i=t.deactivate)&&void 0!==i?i:null,this.provides=null!==(r=t.provides)&&void 0!==r?r:null,this.autoStart=null!==(n=t.autoStart)&&void 0!==n&&n,this.requires=t.requires?t.requires.slice():[],this.optional=t.optional?t.optional.slice():[]}get activated(){return this._activated}set activated(t){this._activated=t}get service(){return this._service}set service(t){this._service=t}get promise(){return this._promise}set promise(t){this._promise=t}}t.createPluginData=function(t){return new i(t)},t.ensureNoCycle=function(t,e,i){const r=[...t.requires,...t.optional],n=r=>{if(r===t.provides)return!0;const o=i.get(r);if(!o)return!1;const a=e.get(o),l=[...a.requires,...a.optional];return 0!==l.length&&(s.push(o),!!l.some(n)||(s.pop(),!1))};if(!t.provides||0===r.length)return;const s=[t.id];if(r.some(n))throw new ReferenceError(`Cycle detected: ${s.join(" -> ")}.`)},t.findDependents=function(t,i,r){const n=new Array,s=t=>{const e=i.get(t),s=[...e.requires,...e.optional];n.push(...s.reduce(((e,i)=>{const n=r.get(i);return n&&e.push([t,n]),e}),[]))};for(const t of i.keys())s(t);const o=n.filter((e=>e[1]===t));let a=0;for(;o.length>a;){const t=o.length,e=new Set(o.map((t=>t[0])));for(const t of e)n.filter((e=>e[1]===t)).forEach((t=>{o.includes(t)||o.push(t)}));a=t}const l=e.topologicSort(o),u=l.findIndex((e=>e===t));return-1===u?[t]:l.slice(0,u+1)},t.collectStartupPlugins=function(t,e){const i=new Set;for(const e of t.keys())!0===t.get(e).autoStart&&i.add(e);if(e.startPlugins)for(const t of e.startPlugins)i.add(t);if(e.ignorePlugins)for(const t of e.ignorePlugins)i.delete(t);return Array.from(i)}}(i||(i={}));function r(t){let e=0;for(let i=0,r=t.length;i<r;++i)i%4==0&&(e=4294967295*Math.random()>>>0),t[i]=255&e,e>>>=8}t.Random=void 0,(t.Random||(t.Random={})).getRandomValues=(()=>{const t="undefined"!=typeof window&&(window.crypto||window.msCrypto)||null;return t&&"function"==typeof t.getRandomValues?function(e){return t.getRandomValues(e)}:r})(),t.UUID=void 0,(t.UUID||(t.UUID={})).uuid4=function(t){const e=new Uint8Array(16),i=new Array(256);for(let t=0;t<16;++t)i[t]="0"+t.toString(16);for(let t=16;t<256;++t)i[t]=t.toString(16);return function(){return t(e),e[6]=64|15&e[6],e[8]=128|63&e[8],i[e[0]]+i[e[1]]+i[e[2]]+i[e[3]]+"-"+i[e[4]]+i[e[5]]+"-"+i[e[6]]+i[e[7]]+"-"+i[e[8]]+i[e[9]]+"-"+i[e[10]]+i[e[11]]+i[e[12]]+i[e[13]]+i[e[14]]+i[e[15]]}}(t.Random.getRandomValues),t.MimeData=class{constructor(){this._types=[],this._values=[]}types(){return this._types.slice()}hasData(t){return-1!==this._types.indexOf(t)}getData(t){let e=this._types.indexOf(t);return-1!==e?this._values[e]:void 0}setData(t,e){this.clearData(t),this._types.push(t),this._values.push(e)}clearData(t){let e=this._types.indexOf(t);-1!==e&&(this._types.splice(e,1),this._values.splice(e,1))}clear(){this._types.length=0,this._values.length=0}},t.PluginRegistry=class{constructor(t={}){this._application=null,this._validatePlugin=()=>!0,this._plugins=new Map,this._services=new Map,t.validatePlugin&&(console.info("Plugins may be rejected by the custom validation plugin method."),this._validatePlugin=t.validatePlugin)}get application(){return this._application}set application(t){if(null!==this._application)throw Error("PluginRegistry.application is already set. It cannot be overridden.");this._application=t}get deferredPlugins(){return Array.from(this._plugins).filter((([t,e])=>"defer"===e.autoStart)).map((([t,e])=>t))}getPluginDescription(t){var e,i;return null!==(i=null===(e=this._plugins.get(t))||void 0===e?void 0:e.description)&&void 0!==i?i:""}hasPlugin(t){return this._plugins.has(t)}isPluginActivated(t){var e,i;return null!==(i=null===(e=this._plugins.get(t))||void 0===e?void 0:e.activated)&&void 0!==i&&i}listPlugins(){return Array.from(this._plugins.keys())}registerPlugin(t){if(this._plugins.has(t.id))throw new TypeError(`Plugin '${t.id}' is already registered.`);if(!this._validatePlugin(t))throw new Error(`Plugin '${t.id}' is not valid.`);const e=i.createPluginData(t);i.ensureNoCycle(e,this._plugins,this._services),e.provides&&this._services.set(e.provides,e.id),this._plugins.set(e.id,e)}registerPlugins(t){for(const e of t)this.registerPlugin(e)}deregisterPlugin(t,e){const i=this._plugins.get(t);if(i){if(i.activated&&!e)throw new Error(`Plugin '${t}' is still active.`);this._plugins.delete(t)}}async activatePlugin(t){const e=this._plugins.get(t);if(!e)throw new ReferenceError(`Plugin '${t}' is not registered.`);if(e.activated)return;if(e.promise)return e.promise;const i=e.requires.map((t=>this.resolveRequiredService(t))),r=e.optional.map((t=>this.resolveOptionalService(t)));return e.promise=Promise.all([...i,...r]).then((t=>e.activate.apply(void 0,[this.application,...t]))).then((t=>{e.service=t,e.activated=!0,e.promise=null})).catch((t=>{throw e.promise=null,t})),e.promise}async activatePlugins(t,e={}){switch(t){case"defer":{const t=this.deferredPlugins.filter((t=>this._plugins.get(t).autoStart)).map((t=>this.activatePlugin(t)));await Promise.all(t);break}case"startUp":{const t=i.collectStartupPlugins(this._plugins,e).map((async t=>{try{return await this.activatePlugin(t)}catch(e){console.error(`Plugin '${t}' failed to activate.`,e)}}));await Promise.all(t);break}}}async deactivatePlugin(t){const e=this._plugins.get(t);if(!e)throw new ReferenceError(`Plugin '${t}' is not registered.`);if(!e.activated)return[];if(!e.deactivate)throw new TypeError(`Plugin '${t}'#deactivate() method missing`);const r=i.findDependents(t,this._plugins,this._services),n=r.map((t=>this._plugins.get(t)));for(const e of n)if(!e.deactivate)throw new TypeError(`Plugin ${e.id}#deactivate() method missing (depends on ${t})`);for(const t of n){const e=[...t.requires,...t.optional].map((t=>{const e=this._services.get(t);return e?this._plugins.get(e).service:null}));await t.deactivate(this.application,...e),t.service=null,t.activated=!1}return r.pop(),r}async resolveRequiredService(t){const e=this._services.get(t);if(!e)throw new TypeError(`No provider for: ${t.name}.`);const i=this._plugins.get(e);return i.activated||await this.activatePlugin(e),i.service}async resolveOptionalService(t){const e=this._services.get(t);if(!e)return null;const i=this._plugins.get(e);if(!i.activated)try{await this.activatePlugin(e)}catch(t){return console.error(t),null}return i.service}},t.PromiseDelegate=class{constructor(){this.promise=new Promise(((t,e)=>{this._resolve=t,this._reject=e}))}resolve(t){(0,this._resolve)(t)}reject(t){(0,this._reject)(t)}},t.Token=class{constructor(t,e){this.name=t,this.description=null!=e?e:"",this._tokenStructuralPropertyT=null}}})); | ||
//# sourceMappingURL=index.min.js.map |
@@ -0,1 +1,3 @@ | ||
import { topologicSort } from '@lumino/algorithm'; | ||
// Copyright (c) Jupyter Development Team. | ||
@@ -282,2 +284,523 @@ // Distributed under the terms of the Modified BSD License. | ||
// Distributed under the terms of the Modified BSD License. | ||
/** | ||
* Plugin registry. | ||
*/ | ||
class PluginRegistry { | ||
constructor(options = {}) { | ||
this._application = null; | ||
this._validatePlugin = () => true; | ||
this._plugins = new Map(); | ||
this._services = new Map(); | ||
if (options.validatePlugin) { | ||
console.info('Plugins may be rejected by the custom validation plugin method.'); | ||
this._validatePlugin = options.validatePlugin; | ||
} | ||
} | ||
/** | ||
* The application object. | ||
* | ||
* It will be provided as first argument to the | ||
* plugins activation and deactivation functions. | ||
* | ||
* It can only be set once. | ||
* | ||
* By default, it is `null`. | ||
*/ | ||
get application() { | ||
return this._application; | ||
} | ||
set application(v) { | ||
if (this._application !== null) { | ||
throw Error('PluginRegistry.application is already set. It cannot be overridden.'); | ||
} | ||
this._application = v; | ||
} | ||
/** | ||
* The list of all the deferred plugins. | ||
*/ | ||
get deferredPlugins() { | ||
return Array.from(this._plugins) | ||
.filter(([id, plugin]) => plugin.autoStart === 'defer') | ||
.map(([id, plugin]) => id); | ||
} | ||
/** | ||
* Get a plugin description. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns The plugin description. | ||
*/ | ||
getPluginDescription(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : ''; | ||
} | ||
/** | ||
* Test whether a plugin is registered with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is registered, `false` otherwise. | ||
*/ | ||
hasPlugin(id) { | ||
return this._plugins.has(id); | ||
} | ||
/** | ||
* Test whether a plugin is activated with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is activated, `false` otherwise. | ||
*/ | ||
isPluginActivated(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.activated) !== null && _b !== void 0 ? _b : false; | ||
} | ||
/** | ||
* List the IDs of the plugins registered with the application. | ||
* | ||
* @returns A new array of the registered plugin IDs. | ||
*/ | ||
listPlugins() { | ||
return Array.from(this._plugins.keys()); | ||
} | ||
/** | ||
* Register a plugin with the application. | ||
* | ||
* @param plugin - The plugin to register. | ||
* | ||
* #### Notes | ||
* An error will be thrown if a plugin with the same ID is already | ||
* registered, or if the plugin has a circular dependency. | ||
* | ||
* If the plugin provides a service which has already been provided | ||
* by another plugin, the new service will override the old service. | ||
*/ | ||
registerPlugin(plugin) { | ||
// Throw an error if the plugin ID is already registered. | ||
if (this._plugins.has(plugin.id)) { | ||
throw new TypeError(`Plugin '${plugin.id}' is already registered.`); | ||
} | ||
if (!this._validatePlugin(plugin)) { | ||
throw new Error(`Plugin '${plugin.id}' is not valid.`); | ||
} | ||
// Create the normalized plugin data. | ||
const data = Private.createPluginData(plugin); | ||
// Ensure the plugin does not cause a cyclic dependency. | ||
Private.ensureNoCycle(data, this._plugins, this._services); | ||
// Add the service token to the service map. | ||
if (data.provides) { | ||
this._services.set(data.provides, data.id); | ||
} | ||
// Add the plugin to the plugin map. | ||
this._plugins.set(data.id, data); | ||
} | ||
/** | ||
* Register multiple plugins with the application. | ||
* | ||
* @param plugins - The plugins to register. | ||
* | ||
* #### Notes | ||
* This calls `registerPlugin()` for each of the given plugins. | ||
*/ | ||
registerPlugins(plugins) { | ||
for (const plugin of plugins) { | ||
this.registerPlugin(plugin); | ||
} | ||
} | ||
/** | ||
* Deregister a plugin with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param force - Whether to deregister the plugin even if it is active. | ||
*/ | ||
deregisterPlugin(id, force) { | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
return; | ||
} | ||
if (plugin.activated && !force) { | ||
throw new Error(`Plugin '${id}' is still active.`); | ||
} | ||
this._plugins.delete(id); | ||
} | ||
/** | ||
* Activate the plugin with the given ID. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A promise which resolves when the plugin is activated | ||
* or rejects with an error if it cannot be activated. | ||
*/ | ||
async activatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Resolve immediately if the plugin is already activated. | ||
if (plugin.activated) { | ||
return; | ||
} | ||
// Return the pending resolver promise if it exists. | ||
if (plugin.promise) { | ||
return plugin.promise; | ||
} | ||
// Resolve the required services for the plugin. | ||
const required = plugin.requires.map(t => this.resolveRequiredService(t)); | ||
// Resolve the optional services for the plugin. | ||
const optional = plugin.optional.map(t => this.resolveOptionalService(t)); | ||
// Setup the resolver promise for the plugin. | ||
plugin.promise = Promise.all([...required, ...optional]) | ||
.then(services => plugin.activate.apply(undefined, [this.application, ...services])) | ||
.then(service => { | ||
plugin.service = service; | ||
plugin.activated = true; | ||
plugin.promise = null; | ||
}) | ||
.catch(error => { | ||
plugin.promise = null; | ||
throw error; | ||
}); | ||
// Return the pending resolver promise. | ||
return plugin.promise; | ||
} | ||
/** | ||
* Activate all the deferred plugins. | ||
* | ||
* @returns A promise which will resolve when each plugin is activated | ||
* or rejects with an error if one cannot be activated. | ||
*/ | ||
async activatePlugins(kind, options = {}) { | ||
switch (kind) { | ||
case 'defer': { | ||
const promises = this.deferredPlugins | ||
.filter(pluginId => this._plugins.get(pluginId).autoStart) | ||
.map(pluginId => { | ||
return this.activatePlugin(pluginId); | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
case 'startUp': { | ||
// Collect the ids of the startup plugins. | ||
const startups = Private.collectStartupPlugins(this._plugins, options); | ||
// Generate the activation promises. | ||
const promises = startups.map(async (id) => { | ||
try { | ||
return await this.activatePlugin(id); | ||
} | ||
catch (error) { | ||
console.error(`Plugin '${id}' failed to activate.`, error); | ||
} | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* Deactivate the plugin and its downstream dependents if and only if the | ||
* plugin and its dependents all support `deactivate`. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A list of IDs of downstream plugins deactivated with this one. | ||
*/ | ||
async deactivatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Bail early if the plugin is not activated. | ||
if (!plugin.activated) { | ||
return []; | ||
} | ||
// Check that this plugin can deactivate. | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin '${id}'#deactivate() method missing`); | ||
} | ||
// Find the optimal deactivation order for plugins downstream of this one. | ||
const manifest = Private.findDependents(id, this._plugins, this._services); | ||
const downstream = manifest.map(id => this._plugins.get(id)); | ||
// Check that all downstream plugins can deactivate. | ||
for (const plugin of downstream) { | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin ${plugin.id}#deactivate() method missing (depends on ${id})`); | ||
} | ||
} | ||
// Deactivate all downstream plugins. | ||
for (const plugin of downstream) { | ||
const services = [...plugin.requires, ...plugin.optional].map(service => { | ||
const id = this._services.get(service); | ||
return id ? this._plugins.get(id).service : null; | ||
}); | ||
// Await deactivation so the next plugins only receive active services. | ||
await plugin.deactivate(this.application, ...services); | ||
plugin.service = null; | ||
plugin.activated = false; | ||
} | ||
// Remove plugin ID and return manifest of deactivated plugins. | ||
manifest.pop(); | ||
return manifest; | ||
} | ||
/** | ||
* Resolve a required service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or rejects with an error if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the required services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveRequiredService(token) { | ||
// Reject the promise if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
throw new TypeError(`No provider for: ${token.name}.`); | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
await this.activatePlugin(id); | ||
} | ||
return plugin.service; | ||
} | ||
/** | ||
* Resolve an optional service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or `null` if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the optional services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveOptionalService(token) { | ||
// Resolve with `null` if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
return null; | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
try { | ||
await this.activatePlugin(id); | ||
} | ||
catch (reason) { | ||
console.error(reason); | ||
return null; | ||
} | ||
} | ||
return plugin.service; | ||
} | ||
} | ||
/** | ||
* The namespace for the module implementation details. | ||
*/ | ||
var Private; | ||
(function (Private) { | ||
class PluginData { | ||
constructor(plugin) { | ||
var _a, _b, _c, _d; | ||
this._activated = false; | ||
this._promise = null; | ||
this._service = null; | ||
this.id = plugin.id; | ||
this.description = (_a = plugin.description) !== null && _a !== void 0 ? _a : ''; | ||
this.activate = plugin.activate; | ||
this.deactivate = (_b = plugin.deactivate) !== null && _b !== void 0 ? _b : null; | ||
this.provides = (_c = plugin.provides) !== null && _c !== void 0 ? _c : null; | ||
this.autoStart = (_d = plugin.autoStart) !== null && _d !== void 0 ? _d : false; | ||
this.requires = plugin.requires ? plugin.requires.slice() : []; | ||
this.optional = plugin.optional ? plugin.optional.slice() : []; | ||
} | ||
/** | ||
* Whether the plugin has been activated. | ||
*/ | ||
get activated() { | ||
return this._activated; | ||
} | ||
set activated(a) { | ||
this._activated = a; | ||
} | ||
/** | ||
* The resolved service for the plugin, or `null`. | ||
*/ | ||
get service() { | ||
return this._service; | ||
} | ||
set service(s) { | ||
this._service = s; | ||
} | ||
/** | ||
* The pending resolver promise, or `null`. | ||
*/ | ||
get promise() { | ||
return this._promise; | ||
} | ||
set promise(p) { | ||
this._promise = p; | ||
} | ||
} | ||
/** | ||
* Create a normalized plugin data object for the given plugin. | ||
*/ | ||
function createPluginData(plugin) { | ||
return new PluginData(plugin); | ||
} | ||
Private.createPluginData = createPluginData; | ||
/** | ||
* Ensure no cycle is present in the plugin resolution graph. | ||
* | ||
* If a cycle is detected, an error will be thrown. | ||
*/ | ||
function ensureNoCycle(plugin, plugins, services) { | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
const visit = (token) => { | ||
if (token === plugin.provides) { | ||
return true; | ||
} | ||
const id = services.get(token); | ||
if (!id) { | ||
return false; | ||
} | ||
const visited = plugins.get(id); | ||
const dependencies = [...visited.requires, ...visited.optional]; | ||
if (dependencies.length === 0) { | ||
return false; | ||
} | ||
trace.push(id); | ||
if (dependencies.some(visit)) { | ||
return true; | ||
} | ||
trace.pop(); | ||
return false; | ||
}; | ||
// Bail early if there cannot be a cycle. | ||
if (!plugin.provides || dependencies.length === 0) { | ||
return; | ||
} | ||
// Setup a stack to trace service resolution. | ||
const trace = [plugin.id]; | ||
// Throw an exception if a cycle is present. | ||
if (dependencies.some(visit)) { | ||
throw new ReferenceError(`Cycle detected: ${trace.join(' -> ')}.`); | ||
} | ||
} | ||
Private.ensureNoCycle = ensureNoCycle; | ||
/** | ||
* Find dependents in deactivation order. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param plugins - The map containing all plugins. | ||
* | ||
* @param services - The map containing all services. | ||
* | ||
* @returns A list of dependent plugin IDs in order of deactivation | ||
* | ||
* #### Notes | ||
* The final item of the returned list is always the plugin of interest. | ||
*/ | ||
function findDependents(id, plugins, services) { | ||
const edges = new Array(); | ||
const add = (id) => { | ||
const plugin = plugins.get(id); | ||
// FIXME In the case of missing optional dependencies, we may consider | ||
// deactivating and reactivating the plugin without the missing service. | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
edges.push(...dependencies.reduce((acc, dep) => { | ||
const service = services.get(dep); | ||
if (service) { | ||
// An edge is oriented from dependent to provider. | ||
acc.push([id, service]); | ||
} | ||
return acc; | ||
}, [])); | ||
}; | ||
for (const id of plugins.keys()) { | ||
add(id); | ||
} | ||
// Filter edges | ||
// - Get all packages that dependent on the package to be deactivated | ||
const newEdges = edges.filter(edge => edge[1] === id); | ||
let oldSize = 0; | ||
while (newEdges.length > oldSize) { | ||
const previousSize = newEdges.length; | ||
// Get all packages that dependent on packages that will be deactivated | ||
const packagesOfInterest = new Set(newEdges.map(edge => edge[0])); | ||
for (const poi of packagesOfInterest) { | ||
edges | ||
.filter(edge => edge[1] === poi) | ||
.forEach(edge => { | ||
// We check it is not already included to deal with circular dependencies | ||
if (!newEdges.includes(edge)) { | ||
newEdges.push(edge); | ||
} | ||
}); | ||
} | ||
oldSize = previousSize; | ||
} | ||
const sorted = topologicSort(newEdges); | ||
const index = sorted.findIndex(candidate => candidate === id); | ||
if (index === -1) { | ||
return [id]; | ||
} | ||
return sorted.slice(0, index + 1); | ||
} | ||
Private.findDependents = findDependents; | ||
/** | ||
* Collect the IDs of the plugins to activate on startup. | ||
*/ | ||
function collectStartupPlugins(plugins, options) { | ||
// Create a set to hold the plugin IDs. | ||
const collection = new Set(); | ||
// Collect the auto-start (non deferred) plugins. | ||
for (const id of plugins.keys()) { | ||
if (plugins.get(id).autoStart === true) { | ||
collection.add(id); | ||
} | ||
} | ||
// Add the startup plugins. | ||
if (options.startPlugins) { | ||
for (const id of options.startPlugins) { | ||
collection.add(id); | ||
} | ||
} | ||
// Remove the ignored plugins. | ||
if (options.ignorePlugins) { | ||
for (const id of options.ignorePlugins) { | ||
collection.delete(id); | ||
} | ||
} | ||
// Return the collected startup plugins. | ||
return Array.from(collection); | ||
} | ||
Private.collectStartupPlugins = collectStartupPlugins; | ||
})(Private || (Private = {})); | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
/*----------------------------------------------------------------------------- | ||
@@ -527,3 +1050,3 @@ | Copyright (c) 2014-2017, PhosphorJS Contributors | ||
export { JSONExt, MimeData, PromiseDelegate, Random, Token, UUID }; | ||
export { JSONExt, MimeData, PluginRegistry, PromiseDelegate, Random, Token, UUID }; | ||
//# sourceMappingURL=index.node.es6.js.map |
'use strict'; | ||
var algorithm = require('@lumino/algorithm'); | ||
// Copyright (c) Jupyter Development Team. | ||
@@ -284,2 +286,523 @@ // Distributed under the terms of the Modified BSD License. | ||
// Distributed under the terms of the Modified BSD License. | ||
/** | ||
* Plugin registry. | ||
*/ | ||
class PluginRegistry { | ||
constructor(options = {}) { | ||
this._application = null; | ||
this._validatePlugin = () => true; | ||
this._plugins = new Map(); | ||
this._services = new Map(); | ||
if (options.validatePlugin) { | ||
console.info('Plugins may be rejected by the custom validation plugin method.'); | ||
this._validatePlugin = options.validatePlugin; | ||
} | ||
} | ||
/** | ||
* The application object. | ||
* | ||
* It will be provided as first argument to the | ||
* plugins activation and deactivation functions. | ||
* | ||
* It can only be set once. | ||
* | ||
* By default, it is `null`. | ||
*/ | ||
get application() { | ||
return this._application; | ||
} | ||
set application(v) { | ||
if (this._application !== null) { | ||
throw Error('PluginRegistry.application is already set. It cannot be overridden.'); | ||
} | ||
this._application = v; | ||
} | ||
/** | ||
* The list of all the deferred plugins. | ||
*/ | ||
get deferredPlugins() { | ||
return Array.from(this._plugins) | ||
.filter(([id, plugin]) => plugin.autoStart === 'defer') | ||
.map(([id, plugin]) => id); | ||
} | ||
/** | ||
* Get a plugin description. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns The plugin description. | ||
*/ | ||
getPluginDescription(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : ''; | ||
} | ||
/** | ||
* Test whether a plugin is registered with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is registered, `false` otherwise. | ||
*/ | ||
hasPlugin(id) { | ||
return this._plugins.has(id); | ||
} | ||
/** | ||
* Test whether a plugin is activated with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns `true` if the plugin is activated, `false` otherwise. | ||
*/ | ||
isPluginActivated(id) { | ||
var _a, _b; | ||
return (_b = (_a = this._plugins.get(id)) === null || _a === void 0 ? void 0 : _a.activated) !== null && _b !== void 0 ? _b : false; | ||
} | ||
/** | ||
* List the IDs of the plugins registered with the application. | ||
* | ||
* @returns A new array of the registered plugin IDs. | ||
*/ | ||
listPlugins() { | ||
return Array.from(this._plugins.keys()); | ||
} | ||
/** | ||
* Register a plugin with the application. | ||
* | ||
* @param plugin - The plugin to register. | ||
* | ||
* #### Notes | ||
* An error will be thrown if a plugin with the same ID is already | ||
* registered, or if the plugin has a circular dependency. | ||
* | ||
* If the plugin provides a service which has already been provided | ||
* by another plugin, the new service will override the old service. | ||
*/ | ||
registerPlugin(plugin) { | ||
// Throw an error if the plugin ID is already registered. | ||
if (this._plugins.has(plugin.id)) { | ||
throw new TypeError(`Plugin '${plugin.id}' is already registered.`); | ||
} | ||
if (!this._validatePlugin(plugin)) { | ||
throw new Error(`Plugin '${plugin.id}' is not valid.`); | ||
} | ||
// Create the normalized plugin data. | ||
const data = Private.createPluginData(plugin); | ||
// Ensure the plugin does not cause a cyclic dependency. | ||
Private.ensureNoCycle(data, this._plugins, this._services); | ||
// Add the service token to the service map. | ||
if (data.provides) { | ||
this._services.set(data.provides, data.id); | ||
} | ||
// Add the plugin to the plugin map. | ||
this._plugins.set(data.id, data); | ||
} | ||
/** | ||
* Register multiple plugins with the application. | ||
* | ||
* @param plugins - The plugins to register. | ||
* | ||
* #### Notes | ||
* This calls `registerPlugin()` for each of the given plugins. | ||
*/ | ||
registerPlugins(plugins) { | ||
for (const plugin of plugins) { | ||
this.registerPlugin(plugin); | ||
} | ||
} | ||
/** | ||
* Deregister a plugin with the application. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param force - Whether to deregister the plugin even if it is active. | ||
*/ | ||
deregisterPlugin(id, force) { | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
return; | ||
} | ||
if (plugin.activated && !force) { | ||
throw new Error(`Plugin '${id}' is still active.`); | ||
} | ||
this._plugins.delete(id); | ||
} | ||
/** | ||
* Activate the plugin with the given ID. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A promise which resolves when the plugin is activated | ||
* or rejects with an error if it cannot be activated. | ||
*/ | ||
async activatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Resolve immediately if the plugin is already activated. | ||
if (plugin.activated) { | ||
return; | ||
} | ||
// Return the pending resolver promise if it exists. | ||
if (plugin.promise) { | ||
return plugin.promise; | ||
} | ||
// Resolve the required services for the plugin. | ||
const required = plugin.requires.map(t => this.resolveRequiredService(t)); | ||
// Resolve the optional services for the plugin. | ||
const optional = plugin.optional.map(t => this.resolveOptionalService(t)); | ||
// Setup the resolver promise for the plugin. | ||
plugin.promise = Promise.all([...required, ...optional]) | ||
.then(services => plugin.activate.apply(undefined, [this.application, ...services])) | ||
.then(service => { | ||
plugin.service = service; | ||
plugin.activated = true; | ||
plugin.promise = null; | ||
}) | ||
.catch(error => { | ||
plugin.promise = null; | ||
throw error; | ||
}); | ||
// Return the pending resolver promise. | ||
return plugin.promise; | ||
} | ||
/** | ||
* Activate all the deferred plugins. | ||
* | ||
* @returns A promise which will resolve when each plugin is activated | ||
* or rejects with an error if one cannot be activated. | ||
*/ | ||
async activatePlugins(kind, options = {}) { | ||
switch (kind) { | ||
case 'defer': { | ||
const promises = this.deferredPlugins | ||
.filter(pluginId => this._plugins.get(pluginId).autoStart) | ||
.map(pluginId => { | ||
return this.activatePlugin(pluginId); | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
case 'startUp': { | ||
// Collect the ids of the startup plugins. | ||
const startups = Private.collectStartupPlugins(this._plugins, options); | ||
// Generate the activation promises. | ||
const promises = startups.map(async (id) => { | ||
try { | ||
return await this.activatePlugin(id); | ||
} | ||
catch (error) { | ||
console.error(`Plugin '${id}' failed to activate.`, error); | ||
} | ||
}); | ||
await Promise.all(promises); | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* Deactivate the plugin and its downstream dependents if and only if the | ||
* plugin and its dependents all support `deactivate`. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @returns A list of IDs of downstream plugins deactivated with this one. | ||
*/ | ||
async deactivatePlugin(id) { | ||
// Reject the promise if the plugin is not registered. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin) { | ||
throw new ReferenceError(`Plugin '${id}' is not registered.`); | ||
} | ||
// Bail early if the plugin is not activated. | ||
if (!plugin.activated) { | ||
return []; | ||
} | ||
// Check that this plugin can deactivate. | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin '${id}'#deactivate() method missing`); | ||
} | ||
// Find the optimal deactivation order for plugins downstream of this one. | ||
const manifest = Private.findDependents(id, this._plugins, this._services); | ||
const downstream = manifest.map(id => this._plugins.get(id)); | ||
// Check that all downstream plugins can deactivate. | ||
for (const plugin of downstream) { | ||
if (!plugin.deactivate) { | ||
throw new TypeError(`Plugin ${plugin.id}#deactivate() method missing (depends on ${id})`); | ||
} | ||
} | ||
// Deactivate all downstream plugins. | ||
for (const plugin of downstream) { | ||
const services = [...plugin.requires, ...plugin.optional].map(service => { | ||
const id = this._services.get(service); | ||
return id ? this._plugins.get(id).service : null; | ||
}); | ||
// Await deactivation so the next plugins only receive active services. | ||
await plugin.deactivate(this.application, ...services); | ||
plugin.service = null; | ||
plugin.activated = false; | ||
} | ||
// Remove plugin ID and return manifest of deactivated plugins. | ||
manifest.pop(); | ||
return manifest; | ||
} | ||
/** | ||
* Resolve a required service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or rejects with an error if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the required services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveRequiredService(token) { | ||
// Reject the promise if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
throw new TypeError(`No provider for: ${token.name}.`); | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
await this.activatePlugin(id); | ||
} | ||
return plugin.service; | ||
} | ||
/** | ||
* Resolve an optional service of a given type. | ||
* | ||
* @param token - The token for the service type of interest. | ||
* | ||
* @returns A promise which resolves to an instance of the requested | ||
* service, or `null` if it cannot be resolved. | ||
* | ||
* #### Notes | ||
* Services are singletons. The same instance will be returned each | ||
* time a given service token is resolved. | ||
* | ||
* If the plugin which provides the service has not been activated, | ||
* resolving the service will automatically activate the plugin. | ||
* | ||
* User code will not typically call this method directly. Instead, | ||
* the optional services for the user's plugins will be resolved | ||
* automatically when the plugin is activated. | ||
*/ | ||
async resolveOptionalService(token) { | ||
// Resolve with `null` if there is no provider for the type. | ||
const id = this._services.get(token); | ||
if (!id) { | ||
return null; | ||
} | ||
// Activate the plugin if necessary. | ||
const plugin = this._plugins.get(id); | ||
if (!plugin.activated) { | ||
try { | ||
await this.activatePlugin(id); | ||
} | ||
catch (reason) { | ||
console.error(reason); | ||
return null; | ||
} | ||
} | ||
return plugin.service; | ||
} | ||
} | ||
/** | ||
* The namespace for the module implementation details. | ||
*/ | ||
var Private; | ||
(function (Private) { | ||
class PluginData { | ||
constructor(plugin) { | ||
var _a, _b, _c, _d; | ||
this._activated = false; | ||
this._promise = null; | ||
this._service = null; | ||
this.id = plugin.id; | ||
this.description = (_a = plugin.description) !== null && _a !== void 0 ? _a : ''; | ||
this.activate = plugin.activate; | ||
this.deactivate = (_b = plugin.deactivate) !== null && _b !== void 0 ? _b : null; | ||
this.provides = (_c = plugin.provides) !== null && _c !== void 0 ? _c : null; | ||
this.autoStart = (_d = plugin.autoStart) !== null && _d !== void 0 ? _d : false; | ||
this.requires = plugin.requires ? plugin.requires.slice() : []; | ||
this.optional = plugin.optional ? plugin.optional.slice() : []; | ||
} | ||
/** | ||
* Whether the plugin has been activated. | ||
*/ | ||
get activated() { | ||
return this._activated; | ||
} | ||
set activated(a) { | ||
this._activated = a; | ||
} | ||
/** | ||
* The resolved service for the plugin, or `null`. | ||
*/ | ||
get service() { | ||
return this._service; | ||
} | ||
set service(s) { | ||
this._service = s; | ||
} | ||
/** | ||
* The pending resolver promise, or `null`. | ||
*/ | ||
get promise() { | ||
return this._promise; | ||
} | ||
set promise(p) { | ||
this._promise = p; | ||
} | ||
} | ||
/** | ||
* Create a normalized plugin data object for the given plugin. | ||
*/ | ||
function createPluginData(plugin) { | ||
return new PluginData(plugin); | ||
} | ||
Private.createPluginData = createPluginData; | ||
/** | ||
* Ensure no cycle is present in the plugin resolution graph. | ||
* | ||
* If a cycle is detected, an error will be thrown. | ||
*/ | ||
function ensureNoCycle(plugin, plugins, services) { | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
const visit = (token) => { | ||
if (token === plugin.provides) { | ||
return true; | ||
} | ||
const id = services.get(token); | ||
if (!id) { | ||
return false; | ||
} | ||
const visited = plugins.get(id); | ||
const dependencies = [...visited.requires, ...visited.optional]; | ||
if (dependencies.length === 0) { | ||
return false; | ||
} | ||
trace.push(id); | ||
if (dependencies.some(visit)) { | ||
return true; | ||
} | ||
trace.pop(); | ||
return false; | ||
}; | ||
// Bail early if there cannot be a cycle. | ||
if (!plugin.provides || dependencies.length === 0) { | ||
return; | ||
} | ||
// Setup a stack to trace service resolution. | ||
const trace = [plugin.id]; | ||
// Throw an exception if a cycle is present. | ||
if (dependencies.some(visit)) { | ||
throw new ReferenceError(`Cycle detected: ${trace.join(' -> ')}.`); | ||
} | ||
} | ||
Private.ensureNoCycle = ensureNoCycle; | ||
/** | ||
* Find dependents in deactivation order. | ||
* | ||
* @param id - The ID of the plugin of interest. | ||
* | ||
* @param plugins - The map containing all plugins. | ||
* | ||
* @param services - The map containing all services. | ||
* | ||
* @returns A list of dependent plugin IDs in order of deactivation | ||
* | ||
* #### Notes | ||
* The final item of the returned list is always the plugin of interest. | ||
*/ | ||
function findDependents(id, plugins, services) { | ||
const edges = new Array(); | ||
const add = (id) => { | ||
const plugin = plugins.get(id); | ||
// FIXME In the case of missing optional dependencies, we may consider | ||
// deactivating and reactivating the plugin without the missing service. | ||
const dependencies = [...plugin.requires, ...plugin.optional]; | ||
edges.push(...dependencies.reduce((acc, dep) => { | ||
const service = services.get(dep); | ||
if (service) { | ||
// An edge is oriented from dependent to provider. | ||
acc.push([id, service]); | ||
} | ||
return acc; | ||
}, [])); | ||
}; | ||
for (const id of plugins.keys()) { | ||
add(id); | ||
} | ||
// Filter edges | ||
// - Get all packages that dependent on the package to be deactivated | ||
const newEdges = edges.filter(edge => edge[1] === id); | ||
let oldSize = 0; | ||
while (newEdges.length > oldSize) { | ||
const previousSize = newEdges.length; | ||
// Get all packages that dependent on packages that will be deactivated | ||
const packagesOfInterest = new Set(newEdges.map(edge => edge[0])); | ||
for (const poi of packagesOfInterest) { | ||
edges | ||
.filter(edge => edge[1] === poi) | ||
.forEach(edge => { | ||
// We check it is not already included to deal with circular dependencies | ||
if (!newEdges.includes(edge)) { | ||
newEdges.push(edge); | ||
} | ||
}); | ||
} | ||
oldSize = previousSize; | ||
} | ||
const sorted = algorithm.topologicSort(newEdges); | ||
const index = sorted.findIndex(candidate => candidate === id); | ||
if (index === -1) { | ||
return [id]; | ||
} | ||
return sorted.slice(0, index + 1); | ||
} | ||
Private.findDependents = findDependents; | ||
/** | ||
* Collect the IDs of the plugins to activate on startup. | ||
*/ | ||
function collectStartupPlugins(plugins, options) { | ||
// Create a set to hold the plugin IDs. | ||
const collection = new Set(); | ||
// Collect the auto-start (non deferred) plugins. | ||
for (const id of plugins.keys()) { | ||
if (plugins.get(id).autoStart === true) { | ||
collection.add(id); | ||
} | ||
} | ||
// Add the startup plugins. | ||
if (options.startPlugins) { | ||
for (const id of options.startPlugins) { | ||
collection.add(id); | ||
} | ||
} | ||
// Remove the ignored plugins. | ||
if (options.ignorePlugins) { | ||
for (const id of options.ignorePlugins) { | ||
collection.delete(id); | ||
} | ||
} | ||
// Return the collected startup plugins. | ||
return Array.from(collection); | ||
} | ||
Private.collectStartupPlugins = collectStartupPlugins; | ||
})(Private || (Private = {})); | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
/*----------------------------------------------------------------------------- | ||
@@ -530,4 +1053,5 @@ | Copyright (c) 2014-2017, PhosphorJS Contributors | ||
exports.MimeData = MimeData; | ||
exports.PluginRegistry = PluginRegistry; | ||
exports.PromiseDelegate = PromiseDelegate; | ||
exports.Token = Token; | ||
//# sourceMappingURL=index.node.js.map |
{ | ||
"name": "@lumino/coreutils", | ||
"version": "2.1.2", | ||
"version": "2.2.0", | ||
"description": "Lumino Core Utilities", | ||
@@ -32,17 +32,14 @@ "homepage": "https://github.com/jupyterlab/lumino", | ||
"build:test": "npm run clean:test && tsc --build tests && cd tests && rollup -c", | ||
"clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", | ||
"clean": "rimraf ./lib *.tsbuildinfo ./types ./dist", | ||
"clean:test": "rimraf tests/lib tests/tsconfig.tsbuildinfo", | ||
"minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", | ||
"test": "npm run test:firefox-headless", | ||
"test:chrome": "cd tests && karma start --browsers=Chrome", | ||
"test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", | ||
"test:firefox": "cd tests && karma start --browsers=Firefox", | ||
"test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", | ||
"test:webkit": "cd tests && karma start --browsers=Webkit", | ||
"test:webkit-headless": "cd tests && karma start --browsers=WebkitHeadless", | ||
"test": "npm run build:test && web-test-runner tests/lib/bundle.test.js --node-resolve --playwright", | ||
"test:debug": "npm run test -- --manual --open", | ||
"test:chromium": "npm run test -- --browsers chromium", | ||
"test:firefox": "npm run test -- --browsers firefox", | ||
"test:webkit": "npm run test -- --browsers webkit", | ||
"watch": "tsc --build --watch" | ||
}, | ||
"typedoc": { | ||
"entryPoint": "./src/index.ts", | ||
"displayName": "coreutils" | ||
"dependencies": { | ||
"@lumino/algorithm": "^2.0.2" | ||
}, | ||
@@ -56,11 +53,6 @@ "devDependencies": { | ||
"@types/mocha": "^2.2.39", | ||
"@web/test-runner": "^0.18.2", | ||
"@web/test-runner-playwright": "^0.11.0", | ||
"chai": "^4.3.4", | ||
"karma": "^6.3.4", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-firefox-launcher": "^2.1.1", | ||
"karma-mocha": "^2.0.1", | ||
"karma-mocha-reporter": "^2.2.5", | ||
"karma-webkit-launcher": "^1.0.2", | ||
"mocha": "^9.0.3", | ||
"playwright": "^1.35.0", | ||
"postcss": "^8.4.24", | ||
@@ -67,0 +59,0 @@ "rimraf": "^5.0.1", |
@@ -12,3 +12,4 @@ // Copyright (c) Jupyter Development Team. | ||
export * from './mime'; | ||
export * from './plugins'; | ||
export * from './promise'; | ||
export * from './token'; |
export * from './json'; | ||
export * from './mime'; | ||
export * from './plugins'; | ||
export * from './promise'; | ||
export * from './token'; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
562382
18
40
6459
1
1
+ Added@lumino/algorithm@^2.0.2
+ Added@lumino/algorithm@2.0.2(transitive)