@jupyterlab/coreutils
Advanced tools
Comparing version 3.0.0-alpha.6 to 3.0.0-alpha.7
@@ -62,3 +62,3 @@ "use strict"; | ||
this._args = args; | ||
this._timer = window.setTimeout(() => { | ||
this._timer = setTimeout(() => { | ||
this._activityStopped.emit({ | ||
@@ -65,0 +65,0 @@ sender: this._sender, |
"use strict"; | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -43,6 +35,4 @@ /** | ||
*/ | ||
list(query) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
throw new Error('DataConnector#list method has not been implemented.'); | ||
}); | ||
async list(query) { | ||
throw new Error('DataConnector#list method has not been implemented.'); | ||
} | ||
@@ -59,6 +49,4 @@ /** | ||
*/ | ||
remove(id) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
throw new Error('DataConnector#remove method has not been implemented.'); | ||
}); | ||
async remove(id) { | ||
throw new Error('DataConnector#remove method has not been implemented.'); | ||
} | ||
@@ -77,8 +65,6 @@ /** | ||
*/ | ||
save(id, value) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
throw new Error('DataConnector#save method has not been implemented.'); | ||
}); | ||
async save(id, value) { | ||
throw new Error('DataConnector#save method has not been implemented.'); | ||
} | ||
} | ||
exports.DataConnector = DataConnector; |
@@ -9,2 +9,3 @@ export * from './activitymonitor'; | ||
export * from './poll'; | ||
export * from './ratelimiter'; | ||
export * from './settingregistry'; | ||
@@ -14,2 +15,3 @@ export * from './statedb'; | ||
export * from './time'; | ||
export * from './tokens'; | ||
export * from './url'; |
@@ -15,2 +15,3 @@ "use strict"; | ||
__export(require("./poll")); | ||
__export(require("./ratelimiter")); | ||
__export(require("./settingregistry")); | ||
@@ -20,2 +21,3 @@ __export(require("./statedb")); | ||
__export(require("./time")); | ||
__export(require("./tokens")); | ||
__export(require("./url")); |
@@ -0,1 +1,2 @@ | ||
import { IDisposable } from '@phosphor/disposable'; | ||
/** | ||
@@ -92,1 +93,23 @@ * A generic interface for change emitter payloads. | ||
} | ||
/** | ||
* A function whose invocations are rate limited and can be stopped after | ||
* invocation before it has fired. | ||
* | ||
* @typeparam T - The resolved type of the underlying function. Defaults to any. | ||
* | ||
* @typeparam U - The rejected type of the underlying function. Defaults to any. | ||
*/ | ||
export interface IRateLimiter<T = any, U = any> extends IDisposable { | ||
/** | ||
* The rate limit in milliseconds. | ||
*/ | ||
readonly limit: number; | ||
/** | ||
* Invoke the rate limited function. | ||
*/ | ||
invoke(): Promise<T>; | ||
/** | ||
* Stop the function if it is mid-flight. | ||
*/ | ||
stop(): Promise<void>; | ||
} |
@@ -57,2 +57,11 @@ /** | ||
/** | ||
* Returns the URL converting this notebook to a certain | ||
* format with nbconvert. | ||
*/ | ||
function getNBConvertURL({ path, format, download }: { | ||
path: string; | ||
format: string; | ||
download: boolean; | ||
}): string; | ||
/** | ||
* Get the authorization token for a Jupyter application. | ||
@@ -59,0 +68,0 @@ */ |
@@ -139,2 +139,15 @@ "use strict"; | ||
/** | ||
* Returns the URL converting this notebook to a certain | ||
* format with nbconvert. | ||
*/ | ||
function getNBConvertURL({ path, format, download }) { | ||
const notebookPath = url_1.URLExt.encodeParts(path); | ||
const url = url_1.URLExt.join(getBaseUrl(), 'nbconvert', format, notebookPath); | ||
if (download) { | ||
return url + '?download=true'; | ||
} | ||
return url; | ||
} | ||
PageConfig.getNBConvertURL = getNBConvertURL; | ||
/** | ||
* Get the authorization token for a Jupyter application. | ||
@@ -141,0 +154,0 @@ */ |
@@ -7,8 +7,8 @@ import { IDisposable } from '@phosphor/disposable'; | ||
* @typeparam T - The resolved type of the factory's promises. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam U - The rejected type of the factory's promises. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam V - The type to extend the phases supported by a poll. | ||
*/ | ||
export interface IPoll<T = any, U = any> { | ||
export interface IPoll<T, U, V extends string> { | ||
/** | ||
@@ -33,3 +33,3 @@ * A signal emitted when the poll is disposed. | ||
*/ | ||
readonly state: IPoll.State<T, U>; | ||
readonly state: IPoll.State<T, U, V>; | ||
/** | ||
@@ -43,7 +43,7 @@ * A promise that resolves when the currently-scheduled tick completes. | ||
*/ | ||
readonly tick: Promise<IPoll<T, U>>; | ||
readonly tick: Promise<IPoll<T, U, V>>; | ||
/** | ||
* A signal emitted when the poll state changes, i.e., a new tick is scheduled. | ||
*/ | ||
readonly ticked: ISignal<IPoll<T, U>, IPoll.State<T, U>>; | ||
readonly ticked: ISignal<IPoll<T, U, V>, IPoll.State<T, U, V>>; | ||
} | ||
@@ -88,4 +88,6 @@ /** | ||
* The phase of the poll when the current tick was scheduled. | ||
* | ||
* @typeparam T - A type for any additional tick phases a poll supports. | ||
*/ | ||
type Phase = 'constructed' | 'disposed' | 'reconnected' | 'refreshed' | 'rejected' | 'resolved' | 'standby' | 'started' | 'stopped' | 'when-rejected' | 'when-resolved'; | ||
type Phase<T extends string> = T | 'constructed' | 'disposed' | 'reconnected' | 'refreshed' | 'rejected' | 'resolved' | 'standby' | 'started' | 'stopped'; | ||
/** | ||
@@ -95,8 +97,8 @@ * Definition of poll state at any given time. | ||
* @typeparam T - The resolved type of the factory's promises. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam U - The rejected type of the factory's promises. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam V - The type to extend the phases supported by a poll. | ||
*/ | ||
type State<T = any, U = any> = { | ||
type State<T, U, V extends string> = { | ||
/** | ||
@@ -117,3 +119,3 @@ * The number of milliseconds until the current tick resolves. | ||
*/ | ||
readonly phase: Phase; | ||
readonly phase: Phase<V>; | ||
/** | ||
@@ -134,4 +136,7 @@ * The timestamp for when this tick was scheduled. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam V - An optional type to extend the phases supported by a poll. | ||
* Defaults to `standby`, which already exists in the `Phase` type. | ||
*/ | ||
export declare class Poll<T = any, U = any> implements IDisposable, IPoll<T, U> { | ||
export declare class Poll<T = any, U = any, V extends string = 'standby'> implements IDisposable, IPoll<T, U, V> { | ||
/** | ||
@@ -142,3 +147,3 @@ * Instantiate a new poll with exponential backoff in case of failure. | ||
*/ | ||
constructor(options: Poll.IOptions<T, U>); | ||
constructor(options: Poll.IOptions<T, U, V>); | ||
/** | ||
@@ -167,3 +172,3 @@ * The name of the poll. | ||
*/ | ||
readonly state: IPoll.State<T, U>; | ||
readonly state: IPoll.State<T, U, V>; | ||
/** | ||
@@ -176,3 +181,3 @@ * A promise that resolves when the poll next ticks. | ||
*/ | ||
readonly ticked: ISignal<this, IPoll.State<T, U>>; | ||
readonly ticked: ISignal<this, IPoll.State<T, U, V>>; | ||
/** | ||
@@ -186,5 +191,26 @@ * Dispose the poll. | ||
* @returns A promise that resolves after tick is scheduled and never rejects. | ||
* | ||
* #### Notes | ||
* The returned promise resolves after the tick is scheduled, but before | ||
* the polling action is run. To wait until after the poll action executes, | ||
* await the `poll.tick` promise: `await poll.refresh(); await poll.tick;` | ||
*/ | ||
refresh(): Promise<void>; | ||
/** | ||
* Schedule the next poll tick. | ||
* | ||
* @param next - The next poll state data to schedule. Defaults to standby. | ||
* | ||
* @param next.cancel - Cancels state transition if function returns `true`. | ||
* | ||
* @returns A promise that resolves when the next poll state is active. | ||
* | ||
* #### Notes | ||
* This method is not meant to be invoked by user code typically. It is public | ||
* to allow poll instances to be composed into classes that schedule ticks. | ||
*/ | ||
schedule(next?: Partial<IPoll.State<T, U, V> & { | ||
cancel: (last: IPoll.State<T, U, V>) => boolean; | ||
}>): Promise<void>; | ||
/** | ||
* Starts the poll. Schedules `started` tick if necessary. | ||
@@ -202,18 +228,2 @@ * | ||
/** | ||
* Schedule the next poll tick. | ||
* | ||
* @param next - The next poll state data to schedule. Defaults to standby. | ||
* | ||
* @param next.cancel - Cancels state transition if function returns `true`. | ||
* | ||
* @returns A promise that resolves when the next poll state is active. | ||
* | ||
* #### Notes | ||
* This method is protected to allow sub-classes to implement methods that can | ||
* schedule poll ticks. | ||
*/ | ||
protected schedule(next?: Partial<IPoll.State & { | ||
cancel: (last: IPoll.State) => boolean; | ||
}>): Promise<void>; | ||
/** | ||
* Execute a new poll factory promise or stand by if necessary. | ||
@@ -241,4 +251,6 @@ */ | ||
* @typeparam U - The rejected type of the factory's promises. | ||
* | ||
* @typeparam V - The type to extend the phases supported by a poll. | ||
*/ | ||
type Factory<T, U> = (state: IPoll.State<T, U>) => Promise<T>; | ||
type Factory<T, U, V extends string> = (state: IPoll.State<T, U, V>) => Promise<T>; | ||
/** | ||
@@ -252,12 +264,16 @@ * Indicates when the poll switches to standby. | ||
* @typeparam T - The resolved type of the factory's promises. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam U - The rejected type of the factory's promises. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam V - The type to extend the phases supported by a poll. | ||
*/ | ||
interface IOptions<T = any, U = any> { | ||
interface IOptions<T, U, V extends string> { | ||
/** | ||
* Whether to begin polling automatically; defaults to `true`. | ||
*/ | ||
auto?: boolean; | ||
/** | ||
* A factory function that is passed a poll tick and returns a poll promise. | ||
*/ | ||
factory: Factory<T, U>; | ||
factory: Factory<T, U, V>; | ||
/** | ||
@@ -283,8 +299,8 @@ * The polling frequency parameters. | ||
standby?: Standby | (() => boolean | Standby); | ||
/** | ||
* If set, a promise which must resolve (or reject) before polling begins. | ||
*/ | ||
when?: Promise<any>; | ||
} | ||
/** | ||
* An interval value that indicates the poll should tick immediately. | ||
*/ | ||
const IMMEDIATE = 0; | ||
/** | ||
* Delays are 32-bit integers in many browsers so intervals need to be capped. | ||
@@ -296,2 +312,6 @@ * | ||
const MAX_INTERVAL = 2147483647; | ||
/** | ||
* An interval value that indicates the poll should never tick. | ||
*/ | ||
const NEVER: number; | ||
} |
205
lib/poll.js
"use strict"; | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -16,2 +8,14 @@ const coreutils_1 = require("@phosphor/coreutils"); | ||
/** | ||
* A function to defer an action immediately. | ||
*/ | ||
const defer = typeof requestAnimationFrame === 'function' | ||
? requestAnimationFrame | ||
: setImmediate; | ||
/** | ||
* A function to cancel a deferred action. | ||
*/ | ||
const cancel = typeof cancelAnimationFrame === 'function' | ||
? cancelAnimationFrame | ||
: clearImmediate; | ||
/** | ||
* A class that wraps an asynchronous function to poll at a regular interval | ||
@@ -25,2 +29,5 @@ * with exponential increases to the interval length if the poll fails. | ||
* Defaults to `any`. | ||
* | ||
* @typeparam V - An optional type to extend the phases supported by a poll. | ||
* Defaults to `standby`, which already exists in the `Phase` type. | ||
*/ | ||
@@ -43,23 +50,5 @@ class Poll { | ||
this.name = options.name || Private.DEFAULT_NAME; | ||
// Schedule poll ticks after `when` promise is settled. | ||
(options.when || Promise.resolve()) | ||
.then(_ => { | ||
if (this.isDisposed) { | ||
return; | ||
} | ||
return this.schedule({ | ||
interval: Private.IMMEDIATE, | ||
phase: 'when-resolved' | ||
}); | ||
}) | ||
.catch(reason => { | ||
if (this.isDisposed) { | ||
return; | ||
} | ||
console.warn(`Poll (${this.name}) started despite rejection.`, reason); | ||
return this.schedule({ | ||
interval: Private.IMMEDIATE, | ||
phase: 'when-rejected' | ||
}); | ||
}); | ||
if ('auto' in options ? options.auto : true) { | ||
defer(() => void this.start()); | ||
} | ||
} | ||
@@ -88,6 +77,6 @@ /** | ||
} | ||
if (interval < 0 || interval > max) { | ||
if ((interval < 0 || interval > max) && interval !== Poll.NEVER) { | ||
throw new Error('Poll interval must be between 0 and max'); | ||
} | ||
if (max > Poll.MAX_INTERVAL) { | ||
if (max > Poll.MAX_INTERVAL && max !== Poll.NEVER) { | ||
throw new Error(`Max interval must be less than ${Poll.MAX_INTERVAL}`); | ||
@@ -150,7 +139,12 @@ } | ||
* @returns A promise that resolves after tick is scheduled and never rejects. | ||
* | ||
* #### Notes | ||
* The returned promise resolves after the tick is scheduled, but before | ||
* the polling action is run. To wait until after the poll action executes, | ||
* await the `poll.tick` promise: `await poll.refresh(); await poll.tick;` | ||
*/ | ||
refresh() { | ||
return this.schedule({ | ||
cancel: last => last.phase === 'refreshed', | ||
interval: Private.IMMEDIATE, | ||
cancel: ({ phase }) => phase === 'refreshed', | ||
interval: Poll.IMMEDIATE, | ||
phase: 'refreshed' | ||
@@ -160,2 +154,55 @@ }); | ||
/** | ||
* Schedule the next poll tick. | ||
* | ||
* @param next - The next poll state data to schedule. Defaults to standby. | ||
* | ||
* @param next.cancel - Cancels state transition if function returns `true`. | ||
* | ||
* @returns A promise that resolves when the next poll state is active. | ||
* | ||
* #### Notes | ||
* This method is not meant to be invoked by user code typically. It is public | ||
* to allow poll instances to be composed into classes that schedule ticks. | ||
*/ | ||
async schedule(next = {}) { | ||
if (this.isDisposed) { | ||
return; | ||
} | ||
// Check if the phase transition should be canceled. | ||
if (next.cancel && next.cancel(this.state)) { | ||
return; | ||
} | ||
// Update poll state. | ||
const last = this.state; | ||
const pending = this._tick; | ||
const scheduled = new coreutils_1.PromiseDelegate(); | ||
const state = Object.assign({ interval: this.frequency.interval, payload: null, phase: 'standby', timestamp: new Date().getTime() }, next); | ||
this._state = state; | ||
this._tick = scheduled; | ||
// Clear the schedule if possible. | ||
if (last.interval === Poll.IMMEDIATE) { | ||
cancel(this._timeout); | ||
} | ||
else { | ||
clearTimeout(this._timeout); | ||
} | ||
// Emit ticked signal, resolve pending promise, and await its settlement. | ||
this._ticked.emit(this.state); | ||
pending.resolve(this); | ||
await pending.promise; | ||
// Schedule next execution and cache its timeout handle. | ||
const execute = () => { | ||
if (this.isDisposed || this.tick !== scheduled.promise) { | ||
return; | ||
} | ||
this._execute(); | ||
}; | ||
this._timeout = | ||
state.interval === Poll.IMMEDIATE | ||
? defer(execute) | ||
: state.interval === Poll.NEVER | ||
? -1 | ||
: setTimeout(execute, state.interval); | ||
} | ||
/** | ||
* Starts the poll. Schedules `started` tick if necessary. | ||
@@ -167,4 +214,4 @@ * | ||
return this.schedule({ | ||
cancel: last => last.phase !== 'standby' && last.phase !== 'stopped', | ||
interval: Private.IMMEDIATE, | ||
cancel: ({ phase }) => phase !== 'constructed' && phase !== 'standby' && phase !== 'stopped', | ||
interval: Poll.IMMEDIATE, | ||
phase: 'started' | ||
@@ -180,4 +227,4 @@ }); | ||
return this.schedule({ | ||
cancel: last => last.phase === 'stopped', | ||
interval: Private.NEVER, | ||
cancel: ({ phase }) => phase === 'stopped', | ||
interval: Poll.NEVER, | ||
phase: 'stopped' | ||
@@ -187,63 +234,2 @@ }); | ||
/** | ||
* Schedule the next poll tick. | ||
* | ||
* @param next - The next poll state data to schedule. Defaults to standby. | ||
* | ||
* @param next.cancel - Cancels state transition if function returns `true`. | ||
* | ||
* @returns A promise that resolves when the next poll state is active. | ||
* | ||
* #### Notes | ||
* This method is protected to allow sub-classes to implement methods that can | ||
* schedule poll ticks. | ||
*/ | ||
schedule(next = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (this.isDisposed) { | ||
return; | ||
} | ||
// The `when` promise in the constructor options acts as a gate. | ||
if (this.state.phase === 'constructed') { | ||
if (next.phase !== 'when-rejected' && next.phase !== 'when-resolved') { | ||
yield this.tick; | ||
} | ||
} | ||
// Check if the phase transition should be canceled. | ||
if (next.cancel && next.cancel(this.state)) { | ||
return; | ||
} | ||
// Update poll state. | ||
const last = this.state; | ||
const pending = this._tick; | ||
const scheduled = new coreutils_1.PromiseDelegate(); | ||
const state = Object.assign({ interval: this.frequency.interval, payload: null, phase: 'standby', timestamp: new Date().getTime() }, next); | ||
this._state = state; | ||
this._tick = scheduled; | ||
// Clear the schedule if possible. | ||
if (last.interval === Private.IMMEDIATE) { | ||
cancelAnimationFrame(this._timeout); | ||
} | ||
else { | ||
clearTimeout(this._timeout); | ||
} | ||
// Emit ticked signal, resolve pending promise, and await its settlement. | ||
this._ticked.emit(this.state); | ||
pending.resolve(this); | ||
yield pending.promise; | ||
// Schedule next execution and cache its timeout handle. | ||
const execute = () => { | ||
if (this.isDisposed || this.tick !== scheduled.promise) { | ||
return; | ||
} | ||
this._execute(); | ||
}; | ||
this._timeout = | ||
state.interval === Private.IMMEDIATE | ||
? requestAnimationFrame(execute) | ||
: state.interval === Private.NEVER | ||
? -1 | ||
: setTimeout(execute, state.interval); | ||
}); | ||
} | ||
/** | ||
* Execute a new poll factory promise or stand by if necessary. | ||
@@ -293,2 +279,6 @@ */ | ||
/** | ||
* An interval value that indicates the poll should tick immediately. | ||
*/ | ||
Poll.IMMEDIATE = 0; | ||
/** | ||
* Delays are 32-bit integers in many browsers so intervals need to be capped. | ||
@@ -300,2 +290,6 @@ * | ||
Poll.MAX_INTERVAL = 2147483647; | ||
/** | ||
* An interval value that indicates the poll should never tick. | ||
*/ | ||
Poll.NEVER = Infinity; | ||
})(Poll = exports.Poll || (exports.Poll = {})); | ||
@@ -308,10 +302,2 @@ /** | ||
/** | ||
* An interval value that indicates the poll should tick immediately. | ||
*/ | ||
Private.IMMEDIATE = 0; | ||
/** | ||
* An interval value that indicates the poll should never tick. | ||
*/ | ||
Private.NEVER = Infinity; | ||
/** | ||
* The default backoff growth rate if `backoff` is `true`. | ||
@@ -340,3 +326,3 @@ */ | ||
Private.DEFAULT_STATE = { | ||
interval: Private.NEVER, | ||
interval: Poll.NEVER, | ||
payload: null, | ||
@@ -350,3 +336,3 @@ phase: 'constructed', | ||
Private.DISPOSED_STATE = { | ||
interval: Private.NEVER, | ||
interval: Poll.NEVER, | ||
payload: null, | ||
@@ -380,2 +366,5 @@ phase: 'disposed', | ||
const { backoff, interval, max } = frequency; | ||
if (interval === Poll.NEVER) { | ||
return interval; | ||
} | ||
const growth = backoff === true ? Private.DEFAULT_BACKOFF : backoff === false ? 1 : backoff; | ||
@@ -382,0 +371,0 @@ const random = getRandomIntInclusive(interval, last.interval * growth); |
@@ -1,5 +0,6 @@ | ||
import { JSONObject, JSONValue, ReadonlyJSONObject, ReadonlyJSONValue, Token } from '@phosphor/coreutils'; | ||
import { JSONObject, JSONValue, ReadonlyJSONObject, ReadonlyJSONValue } from '@phosphor/coreutils'; | ||
import { IDisposable } from '@phosphor/disposable'; | ||
import { ISignal } from '@phosphor/signaling'; | ||
import { IDataConnector } from './interfaces'; | ||
import { ISettingRegistry } from './tokens'; | ||
/** | ||
@@ -54,264 +55,2 @@ * An implementation of a schema validator. | ||
/** | ||
* The setting registry token. | ||
*/ | ||
export declare const ISettingRegistry: Token<ISettingRegistry>; | ||
/** | ||
* A namespace for setting registry interfaces. | ||
*/ | ||
export declare namespace ISettingRegistry { | ||
/** | ||
* The primitive types available in a JSON schema. | ||
*/ | ||
type Primitive = 'array' | 'boolean' | 'null' | 'number' | 'object' | 'string'; | ||
/** | ||
* The settings for a specific plugin. | ||
*/ | ||
interface IPlugin extends JSONObject { | ||
/** | ||
* The name of the plugin. | ||
*/ | ||
id: string; | ||
/** | ||
* The collection of values for a specified plugin. | ||
*/ | ||
data: ISettingBundle; | ||
/** | ||
* The raw user settings data as a string containing JSON with comments. | ||
*/ | ||
raw: string; | ||
/** | ||
* The JSON schema for the plugin. | ||
*/ | ||
schema: ISchema; | ||
/** | ||
* The published version of the NPM package containing the plugin. | ||
*/ | ||
version: string; | ||
} | ||
/** | ||
* A namespace for plugin functionality. | ||
*/ | ||
namespace IPlugin { | ||
/** | ||
* A function that transforms a plugin object before it is consumed by the | ||
* setting registry. | ||
*/ | ||
type Transform = (plugin: IPlugin) => IPlugin; | ||
/** | ||
* The phases during which a transformation may be applied to a plugin. | ||
*/ | ||
type Phase = 'compose' | 'fetch'; | ||
} | ||
/** | ||
* A minimal subset of the formal JSON Schema that describes a property. | ||
*/ | ||
interface IProperty extends JSONObject { | ||
/** | ||
* The default value, if any. | ||
*/ | ||
default?: any; | ||
/** | ||
* The schema description. | ||
*/ | ||
description?: string; | ||
/** | ||
* The schema's child properties. | ||
*/ | ||
properties?: { | ||
[property: string]: IProperty; | ||
}; | ||
/** | ||
* The title of a property. | ||
*/ | ||
title?: string; | ||
/** | ||
* The type or types of the data. | ||
*/ | ||
type?: Primitive | Primitive[]; | ||
} | ||
/** | ||
* A schema type that is a minimal subset of the formal JSON Schema along with | ||
* optional JupyterLab rendering hints. | ||
*/ | ||
interface ISchema extends IProperty { | ||
/** | ||
* Whether the schema is deprecated. | ||
* | ||
* #### Notes | ||
* This flag can be used by functionality that loads this plugin's settings | ||
* from the registry. For example, the setting editor does not display a | ||
* plugin's settings if it is set to `true`. | ||
*/ | ||
'jupyter.lab.setting-deprecated'?: boolean; | ||
/** | ||
* The JupyterLab icon class hint. | ||
*/ | ||
'jupyter.lab.setting-icon-class'?: string; | ||
/** | ||
* The JupyterLab icon label hint. | ||
*/ | ||
'jupyter.lab.setting-icon-label'?: string; | ||
/** | ||
* A flag that indicates plugin should be transformed before being used by | ||
* the setting registry. | ||
* | ||
* #### Notes | ||
* If this value is set to `true`, the setting registry will wait until a | ||
* transformation has been registered (by calling the `transform()` method | ||
* of the registry) for the plugin ID before resolving `load()` promises. | ||
* This means that if the attribute is set to `true` but no transformation | ||
* is registered in time, calls to `load()` a plugin will eventually time | ||
* out and reject. | ||
*/ | ||
'jupyter.lab.transform'?: boolean; | ||
/** | ||
* The JupyterLab shortcuts that are creaed by a plugin's schema. | ||
*/ | ||
'jupyter.lab.shortcuts'?: IShortcut[]; | ||
/** | ||
* The root schema is always an object. | ||
*/ | ||
type: 'object'; | ||
} | ||
/** | ||
* The setting values for a plugin. | ||
*/ | ||
interface ISettingBundle extends JSONObject { | ||
/** | ||
* A composite of the user setting values and the plugin schema defaults. | ||
* | ||
* #### Notes | ||
* The `composite` values will always be a superset of the `user` values. | ||
*/ | ||
composite: JSONObject; | ||
/** | ||
* The user setting values. | ||
*/ | ||
user: JSONObject; | ||
} | ||
/** | ||
* An interface for manipulating the settings of a specific plugin. | ||
*/ | ||
interface ISettings extends IDisposable { | ||
/** | ||
* A signal that emits when the plugin's settings have changed. | ||
*/ | ||
readonly changed: ISignal<this, void>; | ||
/** | ||
* The composite of user settings and extension defaults. | ||
*/ | ||
readonly composite: ReadonlyJSONObject; | ||
/** | ||
* The plugin's ID. | ||
*/ | ||
readonly id: string; | ||
readonly plugin: ISettingRegistry.IPlugin; | ||
/** | ||
* The plugin settings raw text value. | ||
*/ | ||
readonly raw: string; | ||
/** | ||
* The plugin's schema. | ||
*/ | ||
readonly schema: ISettingRegistry.ISchema; | ||
/** | ||
* The user settings. | ||
*/ | ||
readonly user: ReadonlyJSONObject; | ||
/** | ||
* The published version of the NPM package containing these settings. | ||
*/ | ||
readonly version: string; | ||
/** | ||
* Return the defaults in a commented JSON format. | ||
*/ | ||
annotatedDefaults(): string; | ||
/** | ||
* Calculate the default value of a setting by iterating through the schema. | ||
* | ||
* @param key - The name of the setting whose default value is calculated. | ||
* | ||
* @returns A calculated default JSON value for a specific setting. | ||
*/ | ||
default(key: string): JSONValue | undefined; | ||
/** | ||
* Get an individual setting. | ||
* | ||
* @param key - The name of the setting being retrieved. | ||
* | ||
* @returns The setting value. | ||
*/ | ||
get(key: string): { | ||
composite: ReadonlyJSONValue; | ||
user: ReadonlyJSONValue; | ||
}; | ||
/** | ||
* Remove a single setting. | ||
* | ||
* @param key - The name of the setting being removed. | ||
* | ||
* @returns A promise that resolves when the setting is removed. | ||
* | ||
* #### Notes | ||
* This function is asynchronous because it writes to the setting registry. | ||
*/ | ||
remove(key: string): Promise<void>; | ||
/** | ||
* Save all of the plugin's user settings at once. | ||
*/ | ||
save(raw: string): Promise<void>; | ||
/** | ||
* Set a single setting. | ||
* | ||
* @param key - The name of the setting being set. | ||
* | ||
* @param value - The value of the setting. | ||
* | ||
* @returns A promise that resolves when the setting has been saved. | ||
* | ||
* #### Notes | ||
* This function is asynchronous because it writes to the setting registry. | ||
*/ | ||
set(key: string, value: JSONValue): Promise<void>; | ||
/** | ||
* Validates raw settings with comments. | ||
* | ||
* @param raw - The JSON with comments string being validated. | ||
* | ||
* @returns A list of errors or `null` if valid. | ||
*/ | ||
validate(raw: string): ISchemaValidator.IError[] | null; | ||
} | ||
/** | ||
* An interface describing a JupyterLab keyboard shortcut. | ||
*/ | ||
interface IShortcut extends JSONObject { | ||
/** | ||
* The optional arguments passed into the shortcut's command. | ||
*/ | ||
args?: JSONObject; | ||
/** | ||
* The command invoked by the shortcut. | ||
*/ | ||
command: string; | ||
/** | ||
* Whether a keyboard shortcut is disabled. `False` by default. | ||
*/ | ||
disabled?: boolean; | ||
/** | ||
* The key combination of the shortcut. | ||
*/ | ||
keys: string[]; | ||
/** | ||
* The CSS selector applicable to the shortcut. | ||
*/ | ||
selector: string; | ||
} | ||
} | ||
/** | ||
* An implementation of a setting registry. | ||
*/ | ||
export interface ISettingRegistry extends SettingRegistry { | ||
} | ||
/** | ||
* The default implementation of a schema validator. | ||
@@ -357,3 +96,3 @@ */ | ||
*/ | ||
export declare class SettingRegistry { | ||
export declare class SettingRegistry implements ISettingRegistry { | ||
/** | ||
@@ -510,3 +249,3 @@ * Create a new setting registry. | ||
*/ | ||
readonly registry: SettingRegistry; | ||
readonly registry: ISettingRegistry; | ||
/** | ||
@@ -671,3 +410,3 @@ * A signal that emits when the plugin's settings have changed. | ||
*/ | ||
registry: SettingRegistry; | ||
registry: ISettingRegistry; | ||
} | ||
@@ -674,0 +413,0 @@ } |
"use strict"; | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -24,3 +16,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
const ajv_1 = __importDefault(require("ajv")); | ||
const json = __importStar(require("comment-json")); | ||
const json = __importStar(require("json5")); | ||
const coreutils_1 = require("@phosphor/coreutils"); | ||
@@ -44,8 +36,3 @@ const disposable_1 = require("@phosphor/disposable"); | ||
const RECORD_SEPARATOR = String.fromCharCode(30); | ||
/* tslint:disable */ | ||
/** | ||
* The setting registry token. | ||
*/ | ||
exports.ISettingRegistry = new coreutils_1.Token('@jupyterlab/coreutils:ISettingRegistry'); | ||
/** | ||
* The default implementation of a schema validator. | ||
@@ -92,4 +79,3 @@ */ | ||
try { | ||
const strip = true; | ||
user = json.parse(plugin.raw, null, strip); | ||
user = json.parse(plugin.raw, null); | ||
} | ||
@@ -208,16 +194,14 @@ catch (error) { | ||
*/ | ||
get(plugin, key) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Wait for data preload before allowing normal operation. | ||
yield this._ready; | ||
const plugins = this.plugins; | ||
if (plugin in plugins) { | ||
const { composite, user } = plugins[plugin].data; | ||
return { | ||
composite: key in composite ? copy(composite[key]) : undefined, | ||
user: key in user ? copy(user[key]) : undefined | ||
}; | ||
} | ||
return this.load(plugin).then(() => this.get(plugin, key)); | ||
}); | ||
async get(plugin, key) { | ||
// Wait for data preload before allowing normal operation. | ||
await this._ready; | ||
const plugins = this.plugins; | ||
if (plugin in plugins) { | ||
const { composite, user } = plugins[plugin].data; | ||
return { | ||
composite: key in composite ? copy(composite[key]) : undefined, | ||
user: key in user ? copy(user[key]) : undefined | ||
}; | ||
} | ||
return this.load(plugin).then(() => this.get(plugin, key)); | ||
} | ||
@@ -232,15 +216,13 @@ /** | ||
*/ | ||
load(plugin) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Wait for data preload before allowing normal operation. | ||
yield this._ready; | ||
const plugins = this.plugins; | ||
const registry = this; | ||
// If the plugin exists, resolve. | ||
if (plugin in plugins) { | ||
return new Settings({ plugin: plugins[plugin], registry }); | ||
} | ||
// If the plugin needs to be loaded from the data connector, fetch. | ||
return this.reload(plugin); | ||
}); | ||
async load(plugin) { | ||
// Wait for data preload before allowing normal operation. | ||
await this._ready; | ||
const plugins = this.plugins; | ||
const registry = this; | ||
// If the plugin exists, resolve. | ||
if (plugin in plugins) { | ||
return new Settings({ plugin: plugins[plugin], registry }); | ||
} | ||
// If the plugin needs to be loaded from the data connector, fetch. | ||
return this.reload(plugin); | ||
} | ||
@@ -255,13 +237,11 @@ /** | ||
*/ | ||
reload(plugin) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Wait for data preload before allowing normal operation. | ||
yield this._ready; | ||
const fetched = yield this.connector.fetch(plugin); | ||
const plugins = this.plugins; | ||
const registry = this; | ||
yield this._load(yield this._transform('fetch', fetched)); | ||
this._pluginChanged.emit(plugin); | ||
return new Settings({ plugin: plugins[plugin], registry }); | ||
}); | ||
async reload(plugin) { | ||
// Wait for data preload before allowing normal operation. | ||
await this._ready; | ||
const fetched = await this.connector.fetch(plugin); | ||
const plugins = this.plugins; | ||
const registry = this; | ||
await this._load(await this._transform('fetch', fetched)); | ||
this._pluginChanged.emit(plugin); | ||
return new Settings({ plugin: plugins[plugin], registry }); | ||
} | ||
@@ -277,17 +257,15 @@ /** | ||
*/ | ||
remove(plugin, key) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Wait for data preload before allowing normal operation. | ||
yield this._ready; | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
return; | ||
} | ||
const raw = json.parse(plugins[plugin].raw, null, true); | ||
// Delete both the value and any associated comment. | ||
delete raw[key]; | ||
delete raw[`// ${key}`]; | ||
plugins[plugin].raw = Private.annotatedPlugin(plugins[plugin], raw); | ||
return this._save(plugin); | ||
}); | ||
async remove(plugin, key) { | ||
// Wait for data preload before allowing normal operation. | ||
await this._ready; | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
return; | ||
} | ||
const raw = json.parse(plugins[plugin].raw, null); | ||
// Delete both the value and any associated comment. | ||
delete raw[key]; | ||
delete raw[`// ${key}`]; | ||
plugins[plugin].raw = Private.annotatedPlugin(plugins[plugin], raw); | ||
return this._save(plugin); | ||
} | ||
@@ -306,15 +284,13 @@ /** | ||
*/ | ||
set(plugin, key, value) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Wait for data preload before allowing normal operation. | ||
yield this._ready; | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
return this.load(plugin).then(() => this.set(plugin, key, value)); | ||
} | ||
// Parse the raw JSON string removing all comments and return an object. | ||
const raw = json.parse(plugins[plugin].raw, null, true); | ||
plugins[plugin].raw = Private.annotatedPlugin(plugins[plugin], Object.assign({}, raw, { [key]: value })); | ||
return this._save(plugin); | ||
}); | ||
async set(plugin, key, value) { | ||
// Wait for data preload before allowing normal operation. | ||
await this._ready; | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
return this.load(plugin).then(() => this.set(plugin, key, value)); | ||
} | ||
// Parse the raw JSON string removing all comments and return an object. | ||
const raw = json.parse(plugins[plugin].raw, null); | ||
plugins[plugin].raw = Private.annotatedPlugin(plugins[plugin], Object.assign({}, raw, { [key]: value })); | ||
return this._save(plugin); | ||
} | ||
@@ -361,14 +337,12 @@ /** | ||
*/ | ||
upload(plugin, raw) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Wait for data preload before allowing normal operation. | ||
yield this._ready; | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
return this.load(plugin).then(() => this.upload(plugin, raw)); | ||
} | ||
// Set the local copy. | ||
plugins[plugin].raw = raw; | ||
return this._save(plugin); | ||
}); | ||
async upload(plugin, raw) { | ||
// Wait for data preload before allowing normal operation. | ||
await this._ready; | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
return this.load(plugin).then(() => this.upload(plugin, raw)); | ||
} | ||
// Set the local copy. | ||
plugins[plugin].raw = raw; | ||
return this._save(plugin); | ||
} | ||
@@ -378,22 +352,20 @@ /** | ||
*/ | ||
_load(data) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const plugin = data.id; | ||
// Validate and preload the item. | ||
try { | ||
yield this._validate(data); | ||
} | ||
catch (errors) { | ||
const output = [`Validating ${plugin} failed:`]; | ||
errors.forEach((error, index) => { | ||
const { dataPath, schemaPath, keyword, message } = error; | ||
if (dataPath || schemaPath) { | ||
output.push(`${index} - schema @ ${schemaPath}, data @ ${dataPath}`); | ||
} | ||
output.push(`{${keyword}} ${message}`); | ||
}); | ||
console.warn(output.join('\n')); | ||
throw errors; | ||
} | ||
}); | ||
async _load(data) { | ||
const plugin = data.id; | ||
// Validate and preload the item. | ||
try { | ||
await this._validate(data); | ||
} | ||
catch (errors) { | ||
const output = [`Validating ${plugin} failed:`]; | ||
errors.forEach((error, index) => { | ||
const { dataPath, schemaPath, keyword, message } = error; | ||
if (dataPath || schemaPath) { | ||
output.push(`${index} - schema @ ${schemaPath}, data @ ${dataPath}`); | ||
} | ||
output.push(`{${keyword}} ${message}`); | ||
}); | ||
console.warn(output.join('\n')); | ||
throw errors; | ||
} | ||
} | ||
@@ -403,83 +375,77 @@ /** | ||
*/ | ||
_preload(plugins) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield Promise.all(plugins.map((plugin) => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
// Apply a transformation to the plugin if necessary. | ||
yield this._load(yield this._transform('fetch', plugin)); | ||
} | ||
catch (errors) { | ||
/* Ignore preload errors. */ | ||
console.log('Ignored setting registry preload errors.', errors); | ||
} | ||
}))); | ||
}); | ||
} | ||
/** | ||
* Save a plugin in the registry. | ||
*/ | ||
_save(plugin) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
throw new Error(`${plugin} does not exist in setting registry.`); | ||
} | ||
async _preload(plugins) { | ||
await Promise.all(plugins.map(async (plugin) => { | ||
try { | ||
yield this._validate(plugins[plugin]); | ||
// Apply a transformation to the plugin if necessary. | ||
await this._load(await this._transform('fetch', plugin)); | ||
} | ||
catch (errors) { | ||
console.warn(`${plugin} validation errors:`, errors); | ||
throw new Error(`${plugin} failed to validate; check console.`); | ||
/* Ignore preload errors. */ | ||
console.log('Ignored setting registry preload errors.', errors); | ||
} | ||
yield this.connector.save(plugin, plugins[plugin].raw); | ||
// Fetch and reload the data to guarantee server and client are in sync. | ||
const fetched = yield this.connector.fetch(plugin); | ||
yield this._load(yield this._transform('fetch', fetched)); | ||
this._pluginChanged.emit(plugin); | ||
}); | ||
})); | ||
} | ||
/** | ||
* Save a plugin in the registry. | ||
*/ | ||
async _save(plugin) { | ||
const plugins = this.plugins; | ||
if (!(plugin in plugins)) { | ||
throw new Error(`${plugin} does not exist in setting registry.`); | ||
} | ||
try { | ||
await this._validate(plugins[plugin]); | ||
} | ||
catch (errors) { | ||
console.warn(`${plugin} validation errors:`, errors); | ||
throw new Error(`${plugin} failed to validate; check console.`); | ||
} | ||
await this.connector.save(plugin, plugins[plugin].raw); | ||
// Fetch and reload the data to guarantee server and client are in sync. | ||
const fetched = await this.connector.fetch(plugin); | ||
await this._load(await this._transform('fetch', fetched)); | ||
this._pluginChanged.emit(plugin); | ||
} | ||
/** | ||
* Transform the plugin if necessary. | ||
*/ | ||
_transform(phase, plugin, started = new Date().getTime()) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const elapsed = new Date().getTime() - started; | ||
const id = plugin.id; | ||
const transformers = this._transformers; | ||
const timeout = this._timeout; | ||
if (!plugin.schema['jupyter.lab.transform']) { | ||
return plugin; | ||
async _transform(phase, plugin, started = new Date().getTime()) { | ||
const elapsed = new Date().getTime() - started; | ||
const id = plugin.id; | ||
const transformers = this._transformers; | ||
const timeout = this._timeout; | ||
if (!plugin.schema['jupyter.lab.transform']) { | ||
return plugin; | ||
} | ||
if (id in transformers) { | ||
const transformed = transformers[id][phase].call(null, plugin); | ||
if (transformed.id !== id) { | ||
throw [ | ||
{ | ||
dataPath: '', | ||
keyword: 'id', | ||
message: 'Plugin transformations cannot change plugin IDs.', | ||
schemaPath: '' | ||
} | ||
]; | ||
} | ||
if (id in transformers) { | ||
const transformed = transformers[id][phase].call(null, plugin); | ||
if (transformed.id !== id) { | ||
throw [ | ||
{ | ||
dataPath: '', | ||
keyword: 'id', | ||
message: 'Plugin transformations cannot change plugin IDs.', | ||
schemaPath: '' | ||
} | ||
]; | ||
} | ||
return transformed; | ||
return transformed; | ||
} | ||
// If the timeout has not been exceeded, stall and try again in 250ms. | ||
if (elapsed < timeout) { | ||
await new Promise(resolve => { | ||
setTimeout(() => { | ||
resolve(); | ||
}, 250); | ||
}); | ||
return this._transform(phase, plugin, started); | ||
} | ||
throw [ | ||
{ | ||
dataPath: '', | ||
keyword: 'timeout', | ||
message: `Transforming ${plugin.id} timed out.`, | ||
schemaPath: '' | ||
} | ||
// If the timeout has not been exceeded, stall and try again in 250ms. | ||
if (elapsed < timeout) { | ||
yield new Promise(resolve => { | ||
setTimeout(() => { | ||
resolve(); | ||
}, 250); | ||
}); | ||
return this._transform(phase, plugin, started); | ||
} | ||
throw [ | ||
{ | ||
dataPath: '', | ||
keyword: 'timeout', | ||
message: `Transforming ${plugin.id} timed out.`, | ||
schemaPath: '' | ||
} | ||
]; | ||
}); | ||
]; | ||
} | ||
@@ -489,12 +455,10 @@ /** | ||
*/ | ||
_validate(plugin) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Validate the user data and create the composite data. | ||
const errors = this.validator.validateData(plugin); | ||
if (errors) { | ||
throw errors; | ||
} | ||
// Apply a transformation if necessary and set the local copy. | ||
this.plugins[plugin.id] = yield this._transform('compose', plugin); | ||
}); | ||
async _validate(plugin) { | ||
// Validate the user data and create the composite data. | ||
const errors = this.validator.validateData(plugin); | ||
if (errors) { | ||
throw errors; | ||
} | ||
// Apply a transformation if necessary and set the local copy. | ||
this.plugins[plugin.id] = await this._transform('compose', plugin); | ||
} | ||
@@ -501,0 +465,0 @@ } |
@@ -1,35 +0,6 @@ | ||
import { ReadonlyJSONObject, ReadonlyJSONValue, Token } from '@phosphor/coreutils'; | ||
import { ReadonlyJSONObject, ReadonlyJSONValue } from '@phosphor/coreutils'; | ||
import { ISignal } from '@phosphor/signaling'; | ||
import { IDataConnector } from './interfaces'; | ||
import { IStateDB } from './tokens'; | ||
/** | ||
* The default state database token. | ||
*/ | ||
export declare const IStateDB: Token<IStateDB<ReadonlyJSONValue>>; | ||
/** | ||
* The description of a state database. | ||
*/ | ||
export interface IStateDB<T extends ReadonlyJSONValue = ReadonlyJSONValue> extends IDataConnector<T> { | ||
/** | ||
* The maximum allowed length of the data after it has been serialized. | ||
*/ | ||
readonly maxLength: number; | ||
/** | ||
* The namespace prefix for all state database entries. | ||
* | ||
* #### Notes | ||
* This value should be set at instantiation and will only be used | ||
* internally by a state database. That means, for example, that an | ||
* app could have multiple, mutually exclusive state databases. | ||
*/ | ||
readonly namespace: string; | ||
/** | ||
* Return a serialized copy of the state database's entire contents. | ||
* | ||
* @returns A promise that bears the database contents as JSON. | ||
*/ | ||
toJSON(): Promise<{ | ||
[id: string]: T; | ||
}>; | ||
} | ||
/** | ||
* The default concrete implementation of a state database. | ||
@@ -43,3 +14,3 @@ */ | ||
*/ | ||
constructor(options: StateDB.IOptions); | ||
constructor(options?: StateDB.IOptions); | ||
/** | ||
@@ -50,18 +21,5 @@ * A signal that emits the change type any time a value changes. | ||
/** | ||
* The maximum allowed length of the data after it has been serialized. | ||
*/ | ||
readonly maxLength: number; | ||
/** | ||
* The namespace prefix for all state database entries. | ||
* | ||
* #### Notes | ||
* This value should be set at instantiation and will only be used internally | ||
* by a state database. That means, for example, that an app could have | ||
* multiple, mutually exclusive state databases. | ||
*/ | ||
readonly namespace: string; | ||
/** | ||
* Clear the entire database. | ||
*/ | ||
clear(silent?: boolean): Promise<void>; | ||
clear(): Promise<void>; | ||
/** | ||
@@ -134,12 +92,9 @@ * Retrieve a saved bundle from the database. | ||
* | ||
* @returns A promise that bears the database contents as JSON. | ||
* @returns A promise that resolves with the database contents as JSON. | ||
*/ | ||
toJSON(): Promise<{ | ||
[id: string]: T; | ||
readonly [id: string]: T; | ||
}>; | ||
/** | ||
* Clear the entire database. | ||
* | ||
* #### Notes | ||
* Unlike the public `clear` method, this method is synchronous. | ||
*/ | ||
@@ -149,8 +104,9 @@ private _clear; | ||
* Fetch a value from the database. | ||
* | ||
* #### Notes | ||
* Unlike the public `fetch` method, this method is synchronous. | ||
*/ | ||
private _fetch; | ||
/** | ||
* Fetch a list from the database. | ||
*/ | ||
private _list; | ||
/** | ||
* Merge data into the state database. | ||
@@ -165,5 +121,2 @@ */ | ||
* Remove a key in the database. | ||
* | ||
* #### Notes | ||
* Unlike the public `remove` method, this method is synchronous. | ||
*/ | ||
@@ -173,10 +126,7 @@ private _remove; | ||
* Save a key and its value in the database. | ||
* | ||
* #### Notes | ||
* Unlike the public `save` method, this method is synchronous. | ||
*/ | ||
private _save; | ||
private _changed; | ||
private _connector; | ||
private _ready; | ||
private _window; | ||
} | ||
@@ -218,5 +168,5 @@ /** | ||
/** | ||
* The namespace prefix for all state database entries. | ||
* Optional string key/value connector. Defaults to in-memory connector. | ||
*/ | ||
namespace: string; | ||
connector?: IDataConnector<string>; | ||
/** | ||
@@ -228,13 +178,28 @@ * An optional promise that resolves with a data transformation that is | ||
transform?: Promise<DataTransform>; | ||
} | ||
/** | ||
* An in-memory string key/value data connector. | ||
*/ | ||
class Connector implements IDataConnector<string> { | ||
/** | ||
* An optional name for the application window. | ||
* | ||
* #### Notes | ||
* In environments where multiple windows can instantiate a state database, | ||
* a window name is necessary to prefix all keys that are stored within the | ||
* local storage that is shared by all windows. In JupyterLab, this window | ||
* name is generated by the `IWindowResolver` extension. | ||
* Retrieve an item from the data connector. | ||
*/ | ||
windowName?: string; | ||
fetch(id: string): Promise<string>; | ||
/** | ||
* Retrieve the list of items available from the data connector. | ||
*/ | ||
list(query?: string): Promise<{ | ||
ids: string[]; | ||
values: string[]; | ||
}>; | ||
/** | ||
* Remove a value using the data connector. | ||
*/ | ||
remove(id: string): Promise<void>; | ||
/** | ||
* Save a value using the data connector. | ||
*/ | ||
save(id: string, value: string): Promise<void>; | ||
private _storage; | ||
} | ||
} |
"use strict"; | ||
// Copyright (c) Jupyter Development Team. | ||
// Distributed under the terms of the Modified BSD License. | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const coreutils_1 = require("@phosphor/coreutils"); | ||
const signaling_1 = require("@phosphor/signaling"); | ||
/* tslint:disable */ | ||
/** | ||
* The default state database token. | ||
*/ | ||
exports.IStateDB = new coreutils_1.Token('@jupyterlab/coreutils:IStateDB'); | ||
/** | ||
* The default concrete implementation of a state database. | ||
@@ -29,11 +15,6 @@ */ | ||
*/ | ||
constructor(options) { | ||
/** | ||
* The maximum allowed length of the data after it has been serialized. | ||
*/ | ||
this.maxLength = 2000; | ||
constructor(options = {}) { | ||
this._changed = new signaling_1.Signal(this); | ||
const { namespace, transform, windowName } = options; | ||
this.namespace = namespace; | ||
this._window = windowName || ''; | ||
const { connector, transform } = options; | ||
this._connector = connector || new StateDB.Connector(); | ||
this._ready = (transform || Promise.resolve(null)).then(transformation => { | ||
@@ -48,10 +29,7 @@ if (!transformation) { | ||
case 'clear': | ||
this._clear(); | ||
return; | ||
return this._clear(); | ||
case 'merge': | ||
this._merge(contents || {}); | ||
return; | ||
return this._merge(contents || {}); | ||
case 'overwrite': | ||
this._overwrite(contents || {}); | ||
return; | ||
return this._overwrite(contents || {}); | ||
default: | ||
@@ -71,10 +49,5 @@ return; | ||
*/ | ||
clear(silent = false) { | ||
return this._ready.then(() => { | ||
this._clear(); | ||
if (silent) { | ||
return; | ||
} | ||
this._changed.emit({ id: null, type: 'clear' }); | ||
}); | ||
async clear() { | ||
await this._ready; | ||
await this._clear(); | ||
} | ||
@@ -99,7 +72,5 @@ /** | ||
*/ | ||
fetch(id) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const value = yield this._ready.then(() => this._fetch(id)); | ||
return value; | ||
}); | ||
async fetch(id) { | ||
await this._ready; | ||
return this._fetch(id); | ||
} | ||
@@ -122,8 +93,5 @@ /** | ||
*/ | ||
list(namespace) { | ||
return this._ready.then(() => { | ||
const prefix = `${this._window}:${this.namespace}:`; | ||
const mask = (key) => key.replace(prefix, ''); | ||
return Private.fetchNamespace(`${prefix}${namespace}:`, mask); | ||
}); | ||
async list(namespace) { | ||
await this._ready; | ||
return this._list(namespace); | ||
} | ||
@@ -137,7 +105,6 @@ /** | ||
*/ | ||
remove(id) { | ||
return this._ready.then(() => { | ||
this._remove(id); | ||
this._changed.emit({ id, type: 'remove' }); | ||
}); | ||
async remove(id) { | ||
await this._ready; | ||
await this._remove(id); | ||
this._changed.emit({ id, type: 'remove' }); | ||
} | ||
@@ -160,7 +127,6 @@ /** | ||
*/ | ||
save(id, value) { | ||
return this._ready.then(() => { | ||
this._save(id, value); | ||
this._changed.emit({ id, type: 'save' }); | ||
}); | ||
async save(id, value) { | ||
await this._ready; | ||
await this._save(id, value); | ||
this._changed.emit({ id, type: 'save' }); | ||
} | ||
@@ -170,50 +136,42 @@ /** | ||
* | ||
* @returns A promise that bears the database contents as JSON. | ||
* @returns A promise that resolves with the database contents as JSON. | ||
*/ | ||
toJSON() { | ||
return this._ready.then(() => { | ||
const prefix = `${this._window}:${this.namespace}:`; | ||
const mask = (key) => key.replace(prefix, ''); | ||
return Private.toJSON(prefix, mask); | ||
}); | ||
async toJSON() { | ||
await this._ready; | ||
const { ids, values } = await this._list(); | ||
return values.reduce((acc, val, idx) => { | ||
acc[ids[idx]] = val; | ||
return acc; | ||
}, {}); | ||
} | ||
/** | ||
* Clear the entire database. | ||
* | ||
* #### Notes | ||
* Unlike the public `clear` method, this method is synchronous. | ||
*/ | ||
_clear() { | ||
const { localStorage } = window; | ||
const prefix = `${this._window}:${this.namespace}:`; | ||
let i = localStorage.length; | ||
while (i) { | ||
let key = localStorage.key(--i); | ||
if (key && key.indexOf(prefix) === 0) { | ||
localStorage.removeItem(key); | ||
} | ||
} | ||
async _clear() { | ||
await Promise.all((await this._list()).ids.map(id => this._remove(id))); | ||
} | ||
/** | ||
* Fetch a value from the database. | ||
* | ||
* #### Notes | ||
* Unlike the public `fetch` method, this method is synchronous. | ||
*/ | ||
_fetch(id) { | ||
const key = `${this._window}:${this.namespace}:${id}`; | ||
const value = window.localStorage.getItem(key); | ||
async _fetch(id) { | ||
const value = await this._connector.fetch(id); | ||
if (value) { | ||
const envelope = JSON.parse(value); | ||
return envelope.v; | ||
return JSON.parse(value).v; | ||
} | ||
return undefined; | ||
} | ||
/** | ||
* Fetch a list from the database. | ||
*/ | ||
async _list(query) { | ||
const { ids, values } = await this._connector.list(query); | ||
return { | ||
ids, | ||
values: values.map(val => JSON.parse(val).v) | ||
}; | ||
} | ||
/** | ||
* Merge data into the state database. | ||
*/ | ||
_merge(contents) { | ||
Object.keys(contents).forEach(key => { | ||
this._save(key, contents[key]); | ||
}); | ||
async _merge(contents) { | ||
await Promise.all(Object.keys(contents).map(key => this._save(key, contents[key]))); | ||
} | ||
@@ -223,89 +181,63 @@ /** | ||
*/ | ||
_overwrite(contents) { | ||
this._clear(); | ||
this._merge(contents); | ||
async _overwrite(contents) { | ||
await this._clear(); | ||
await this._merge(contents); | ||
} | ||
/** | ||
* Remove a key in the database. | ||
* | ||
* #### Notes | ||
* Unlike the public `remove` method, this method is synchronous. | ||
*/ | ||
_remove(id) { | ||
const key = `${this._window}:${this.namespace}:${id}`; | ||
window.localStorage.removeItem(key); | ||
async _remove(id) { | ||
return this._connector.remove(id); | ||
} | ||
/** | ||
* Save a key and its value in the database. | ||
* | ||
* #### Notes | ||
* Unlike the public `save` method, this method is synchronous. | ||
*/ | ||
_save(id, value) { | ||
const key = `${this._window}:${this.namespace}:${id}`; | ||
const envelope = { v: value }; | ||
const serialized = JSON.stringify(envelope); | ||
const length = serialized.length; | ||
const max = this.maxLength; | ||
if (length > max) { | ||
throw new Error(`Data length (${length}) exceeds maximum (${max})`); | ||
} | ||
window.localStorage.setItem(key, serialized); | ||
async _save(id, value) { | ||
return this._connector.save(id, JSON.stringify({ v: value })); | ||
} | ||
} | ||
exports.StateDB = StateDB; | ||
/* | ||
* A namespace for private module data. | ||
/** | ||
* A namespace for StateDB statics. | ||
*/ | ||
var Private; | ||
(function (Private) { | ||
(function (StateDB) { | ||
/** | ||
* Retrieve all the saved bundles for a given namespace in local storage. | ||
* | ||
* @param prefix - The namespace to retrieve. | ||
* | ||
* @param mask - Optional mask function to transform each key retrieved. | ||
* | ||
* @returns A collection of data payloads for a given prefix. | ||
* | ||
* #### Notes | ||
* If there are any errors in retrieving the data, they will be logged to the | ||
* console in order to optimistically return any extant data without failing. | ||
* An in-memory string key/value data connector. | ||
*/ | ||
function fetchNamespace(namespace, mask = key => key) { | ||
const { localStorage } = window; | ||
let ids = []; | ||
let values = []; | ||
let i = localStorage.length; | ||
while (i) { | ||
let key = localStorage.key(--i); | ||
if (key && key.indexOf(namespace) === 0) { | ||
let value = localStorage.getItem(key); | ||
try { | ||
let envelope = JSON.parse(value); | ||
let id = mask(key); | ||
values[ids.push(id) - 1] = envelope ? envelope.v : undefined; | ||
class Connector { | ||
constructor() { | ||
this._storage = {}; | ||
} | ||
/** | ||
* Retrieve an item from the data connector. | ||
*/ | ||
async fetch(id) { | ||
return this._storage[id]; | ||
} | ||
/** | ||
* Retrieve the list of items available from the data connector. | ||
*/ | ||
async list(query = '') { | ||
return Object.keys(this._storage).reduce((acc, val) => { | ||
if (val && val.indexOf(query) === 0) { | ||
acc.ids.push(val); | ||
acc.values.push(this._storage[val]); | ||
} | ||
catch (error) { | ||
console.warn(error); | ||
localStorage.removeItem(key); | ||
} | ||
} | ||
return acc; | ||
}, { ids: [], values: [] }); | ||
} | ||
return { ids, values }; | ||
/** | ||
* Remove a value using the data connector. | ||
*/ | ||
async remove(id) { | ||
delete this._storage[id]; | ||
} | ||
/** | ||
* Save a value using the data connector. | ||
*/ | ||
async save(id, value) { | ||
this._storage[id] = value; | ||
} | ||
} | ||
Private.fetchNamespace = fetchNamespace; | ||
/** | ||
* Return a serialized copy of a namespace's contents from local storage. | ||
* | ||
* @returns The namespace contents as JSON. | ||
*/ | ||
function toJSON(namespace, mask = key => key) { | ||
const { ids, values } = fetchNamespace(namespace, mask); | ||
return values.reduce((acc, val, idx) => { | ||
acc[ids[idx]] = val; | ||
return acc; | ||
}, {}); | ||
} | ||
Private.toJSON = toJSON; | ||
})(Private || (Private = {})); | ||
StateDB.Connector = Connector; | ||
})(StateDB = exports.StateDB || (exports.StateDB = {})); |
@@ -97,3 +97,3 @@ "use strict"; | ||
function objectToQueryString(value) { | ||
const keys = Object.keys(value); | ||
const keys = Object.keys(value).filter(key => key.length > 0); | ||
if (!keys.length) { | ||
@@ -120,3 +120,5 @@ return ''; | ||
const [key, value] = val.split('='); | ||
acc[key] = decodeURIComponent(value || ''); | ||
if (key.length > 0) { | ||
acc[key] = decodeURIComponent(value || ''); | ||
} | ||
return acc; | ||
@@ -123,0 +125,0 @@ }, {}); |
{ | ||
"name": "@jupyterlab/coreutils", | ||
"version": "3.0.0-alpha.6", | ||
"version": "3.0.0-alpha.7", | ||
"description": "JupyterLab - Core Utilities", | ||
@@ -9,2 +9,6 @@ "homepage": "https://github.com/jupyterlab/jupyterlab", | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/jupyterlab/jupyterlab.git" | ||
}, | ||
"license": "BSD-3-Clause", | ||
@@ -23,6 +27,2 @@ "author": "Project Jupyter", | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/jupyterlab/jupyterlab.git" | ||
}, | ||
"scripts": { | ||
@@ -40,5 +40,5 @@ "build": "tsc -b", | ||
"ajv": "^6.5.5", | ||
"comment-json": "^1.1.3", | ||
"json5": "^2.1.0", | ||
"minimist": "~1.2.0", | ||
"moment": "~2.21.0", | ||
"moment": "^2.24.0", | ||
"path-posix": "~1.0.0", | ||
@@ -48,4 +48,4 @@ "url-parse": "~1.4.3" | ||
"devDependencies": { | ||
"@types/comment-json": "^1.1.0", | ||
"@types/minimist": "~1.2.0", | ||
"@types/json5": "^0.0.30", | ||
"@types/minimist": "^1.2.0", | ||
"rimraf": "~2.6.2", | ||
@@ -58,3 +58,6 @@ "typedoc": "^0.14.2", | ||
}, | ||
"gitHead": "25152cd7c0a2d2d3d020dcd59451c236e9ecc6ab" | ||
"jupyterlab": { | ||
"coreDependency": true | ||
}, | ||
"gitHead": "f8e210a027f6545b426caf4ab64c75a1e1619a44" | ||
} |
# @jupyterlab/coreutils | ||
A JupyterLab package which provides utility functions that are widely used across many | ||
of the `@jupyterlab` packages. This includes (among other things) functions for manipulating paths, urls, and the notebook format. | ||
A JupyterLab package which provides utility functions that are widely used | ||
across many of the `@jupyterlab` packages. This includes (among other things) | ||
functions for manipulating paths, urls, and the notebook format. | ||
This package is intended for use within both Node.js and browser environments. |
166523
36
4964
8
+ Addedjson5@^2.1.0
+ Addedjson5@2.2.3(transitive)
+ Addedmoment@2.30.1(transitive)
- Removedcomment-json@^1.1.3
- Removedcomment-json@1.1.3(transitive)
- Removedesprima@2.7.3(transitive)
- Removedjson-parser@1.1.5(transitive)
- Removedmoment@2.21.0(transitive)
Updatedmoment@^2.24.0