@jbrowse/product-core 4.3.0 → 5.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/RootModel/BaseRootModel.d.ts +514 -323
- package/esm/RootModel/BaseRootModel.js +141 -14
- package/esm/RootModel/HierarchicalConfig.d.ts +30 -20
- package/esm/RootModel/HierarchicalConfig.js +36 -0
- package/esm/RootModel/InternetAccounts.d.ts +18 -1
- package/esm/RootModel/InternetAccounts.js +120 -22
- package/esm/RootModel/PreferencesConfig.d.ts +37 -0
- package/esm/RootModel/PreferencesConfig.js +75 -0
- package/esm/RootModel/createConfigModel.d.ts +257 -0
- package/esm/RootModel/createConfigModel.js +77 -0
- package/esm/RootModel/index.d.ts +10 -5
- package/esm/RootModel/index.js +10 -5
- package/esm/RootModel/menuItems.d.ts +24 -0
- package/esm/RootModel/menuItems.js +141 -0
- package/esm/Session/BaseSession.d.ts +285 -11
- package/esm/Session/BaseSession.js +383 -15
- package/esm/Session/Connections.d.ts +115 -8
- package/esm/Session/Connections.js +196 -74
- package/esm/Session/DrawerWidgets.d.ts +145 -13
- package/esm/Session/DrawerWidgets.js +219 -49
- package/esm/Session/MultipleViews.d.ts +1067 -1177
- package/esm/Session/MultipleViews.js +395 -64
- package/esm/Session/Preferences.d.ts +1389 -0
- package/esm/Session/Preferences.js +58 -0
- package/esm/Session/ReferenceManagement.d.ts +28 -3
- package/esm/Session/ReferenceManagement.js +56 -41
- package/esm/Session/SessionTracks.d.ts +1101 -1180
- package/esm/Session/SessionTracks.js +584 -34
- package/esm/Session/Themes.d.ts +136 -2
- package/esm/Session/Themes.js +267 -21
- package/esm/Session/TrackMenu.d.ts +88 -0
- package/esm/Session/TrackMenu.js +175 -0
- package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
- package/esm/Session/TrackMenuSessionMixin.js +42 -0
- package/esm/Session/Tracks.d.ts +945 -1159
- package/esm/Session/Tracks.js +229 -30
- package/esm/Session/assertSessionModel.d.ts +21 -0
- package/esm/Session/assertSessionModel.js +1 -0
- package/esm/Session/copyTrackSnapshot.d.ts +21 -0
- package/esm/Session/copyTrackSnapshot.js +20 -0
- package/esm/Session/developerMode.d.ts +7 -0
- package/esm/Session/developerMode.js +43 -0
- package/esm/Session/finalizeSession.d.ts +15 -0
- package/esm/Session/finalizeSession.js +28 -0
- package/esm/Session/hydratedForms.d.ts +16 -0
- package/esm/Session/hydratedForms.js +13 -0
- package/esm/Session/index.d.ts +27 -10
- package/esm/Session/index.js +16 -10
- package/esm/Session/pluginExtraTrackItems.d.ts +11 -0
- package/esm/Session/pluginExtraTrackItems.js +13 -0
- package/esm/Session/readdedTrackConf.d.ts +8 -0
- package/esm/Session/readdedTrackConf.js +24 -0
- package/esm/Session/shareableSnapshot.d.ts +23 -0
- package/esm/Session/shareableSnapshot.js +66 -0
- package/esm/Session/temporaryAssemblyTracks.d.ts +24 -0
- package/esm/Session/temporaryAssemblyTracks.js +37 -0
- package/esm/assertExtends.d.ts +55 -0
- package/esm/assertExtends.js +1 -0
- package/esm/controllerTracks.d.ts +76 -0
- package/esm/controllerTracks.js +125 -0
- package/esm/destroyViewState.d.ts +21 -0
- package/esm/destroyViewState.js +29 -0
- package/esm/embeddedController.d.ts +57 -0
- package/esm/embeddedController.js +100 -0
- package/esm/index.d.ts +32 -4
- package/esm/index.js +31 -4
- package/esm/loadPlugins.d.ts +38 -0
- package/esm/loadPlugins.js +45 -0
- package/esm/localFiles.d.ts +74 -0
- package/esm/localFiles.js +156 -0
- package/esm/observeSession.d.ts +55 -0
- package/esm/observeSession.js +90 -0
- package/esm/parallelChunkLoading.d.ts +30 -0
- package/esm/parallelChunkLoading.js +71 -0
- package/esm/pluginInput.d.ts +32 -0
- package/esm/pluginInput.js +19 -0
- package/esm/pruneUnbuildableNodes.d.ts +109 -0
- package/esm/pruneUnbuildableNodes.js +562 -0
- package/esm/resolveAssemblies.d.ts +68 -0
- package/esm/resolveAssemblies.js +77 -0
- package/esm/rpcWorker.d.ts +36 -4
- package/esm/rpcWorker.js +126 -20
- package/esm/sessionMigrations/index.d.ts +15 -0
- package/esm/sessionMigrations/index.js +178 -0
- package/esm/sessionUrl.d.ts +40 -0
- package/esm/sessionUrl.js +66 -0
- package/esm/sessionUtils.d.ts +74 -0
- package/esm/sessionUtils.js +599 -0
- package/esm/siblingCast.d.ts +14 -0
- package/esm/siblingCast.js +16 -0
- package/esm/snapshotUtils.d.ts +8 -0
- package/esm/snapshotUtils.js +12 -0
- package/esm/ui/AboutDialog.d.ts +8 -3
- package/esm/ui/AboutDialog.js +14 -5
- package/esm/ui/AboutDialogContents.d.ts +2 -6
- package/esm/ui/AboutDialogContents.js +13 -17
- package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
- package/esm/ui/AssemblyInfoPanel.js +63 -0
- package/esm/ui/DescriptionPanel.d.ts +3 -0
- package/esm/ui/DescriptionPanel.js +25 -0
- package/esm/ui/Drawer.d.ts +21 -0
- package/esm/ui/Drawer.js +60 -0
- package/esm/ui/DrawerControls.d.ts +5 -0
- package/esm/ui/DrawerControls.js +28 -0
- package/esm/ui/DrawerHeader.d.ts +6 -0
- package/esm/ui/DrawerHeader.js +36 -0
- package/esm/ui/DrawerWidget.d.ts +5 -0
- package/esm/ui/DrawerWidget.js +12 -0
- package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
- package/esm/ui/DrawerWidgetSelector.js +40 -0
- package/esm/ui/FileInfoPanel.d.ts +3 -6
- package/esm/ui/FileInfoPanel.js +36 -26
- package/esm/ui/GeneralPreferencesTab.d.ts +17 -0
- package/esm/ui/GeneralPreferencesTab.js +34 -0
- package/esm/ui/HeaderButtons.d.ts +2 -1
- package/esm/ui/HeaderButtons.js +15 -17
- package/esm/ui/ModalWidget.d.ts +13 -0
- package/esm/ui/ModalWidget.js +48 -0
- package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
- package/esm/ui/ModalWidgetAppBar.js +9 -0
- package/esm/ui/PreferenceCheckbox.d.ts +6 -0
- package/esm/ui/PreferenceCheckbox.js +16 -0
- package/esm/ui/PreferencesDialog.d.ts +34 -0
- package/esm/ui/PreferencesDialog.js +55 -0
- package/esm/ui/PreferencesResetDialog.d.ts +8 -0
- package/esm/ui/PreferencesResetDialog.js +19 -0
- package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
- package/esm/ui/RefNameAliasesDialog.js +65 -0
- package/esm/ui/RefNameInfoDialog.d.ts +3 -6
- package/esm/ui/RefNameInfoDialog.js +45 -63
- package/esm/ui/ViewPreferencesTab.d.ts +12 -0
- package/esm/ui/ViewPreferencesTab.js +15 -0
- package/esm/ui/WidgetBody.d.ts +18 -0
- package/esm/ui/WidgetBody.js +22 -0
- package/esm/ui/WidgetHeading.d.ts +7 -0
- package/esm/ui/WidgetHeading.js +7 -0
- package/esm/ui/aboutTestUtils.d.ts +140 -0
- package/esm/ui/aboutTestUtils.js +25 -0
- package/esm/ui/descriptionHtml.d.ts +25 -0
- package/esm/ui/descriptionHtml.js +64 -0
- package/esm/ui/drawerLayout.d.ts +18 -0
- package/esm/ui/drawerLayout.js +22 -0
- package/esm/ui/index.d.ts +15 -1
- package/esm/ui/index.js +13 -1
- package/esm/ui/preferencesReset.d.ts +18 -0
- package/esm/ui/preferencesReset.js +69 -0
- package/esm/ui/util.d.ts +22 -7
- package/esm/ui/util.js +19 -41
- package/esm/useEngineLifecycle.d.ts +19 -0
- package/esm/useEngineLifecycle.js +44 -0
- package/package.json +15 -18
- package/esm/RootModel/FormatAbout.d.ts +0 -12
- package/esm/RootModel/FormatAbout.js +0 -15
- package/esm/RootModel/FormatDetails.d.ts +0 -24
- package/esm/RootModel/FormatDetails.js +0 -27
- package/esm/Session/DialogQueue.d.ts +0 -17
- package/esm/Session/DialogQueue.js +0 -31
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { LooseSearchIndex } from '@jbrowse/core/util/expandLooseSearchIndex';
|
|
2
|
+
export type AssemblyConfig = Record<string, unknown>;
|
|
3
|
+
export interface TextSearchAdapterConfig {
|
|
4
|
+
type: string;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
type SearchAdapters = TextSearchAdapterConfig[] | undefined;
|
|
8
|
+
export interface HubTrackConfig {
|
|
9
|
+
trackId: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The shapes an assembly can take, discriminated at resolve time: a sequence
|
|
14
|
+
* file URL (`'.../hg38.fa.gz'`, `.2bit`, ...) built into an assembly via
|
|
15
|
+
* `makeAssembly`; a hub name (`'hg38'`, `'GCF_...'`) fetched from jbrowse.org;
|
|
16
|
+
* a full hub config (as `fetchHub` returns); or a bare assembly config (e.g.
|
|
17
|
+
* from `makeAssembly`) — the latter two both being plain config objects.
|
|
18
|
+
*/
|
|
19
|
+
export type AssemblyInput = string | AssemblyConfig;
|
|
20
|
+
export interface ResolvedAssembly {
|
|
21
|
+
assembly: AssemblyConfig;
|
|
22
|
+
/** whatever search adapters the hub this came from carried */
|
|
23
|
+
aggregateTextSearchAdapters?: SearchAdapters;
|
|
24
|
+
/**
|
|
25
|
+
* the hub's track catalog, which its search index names its hits against, so
|
|
26
|
+
* a host keeping the index keeps these too
|
|
27
|
+
*/
|
|
28
|
+
tracks?: HubTrackConfig[];
|
|
29
|
+
}
|
|
30
|
+
export interface ResolvedAssemblies<Track extends object = HubTrackConfig, Index extends LooseSearchIndex = TextSearchAdapterConfig> {
|
|
31
|
+
assemblies: AssemblyConfig[];
|
|
32
|
+
/** the hubs' search adapters, then the host's own */
|
|
33
|
+
aggregateTextSearchAdapters?: (TextSearchAdapterConfig | Index)[];
|
|
34
|
+
/** the hubs' track catalogs, then the host's own tracks */
|
|
35
|
+
tracks?: (HubTrackConfig | Track)[];
|
|
36
|
+
}
|
|
37
|
+
/** What a host already has, to merge with what its hubs bring. */
|
|
38
|
+
export interface HostCatalog<Track extends object, Index extends LooseSearchIndex> {
|
|
39
|
+
tracks?: readonly Track[];
|
|
40
|
+
aggregateTextSearchAdapters?: readonly Index[];
|
|
41
|
+
}
|
|
42
|
+
/** One assembly in any of the four accepted shapes. */
|
|
43
|
+
export declare function resolveAssembly(input: AssemblyInput): Promise<ResolvedAssembly>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a whole list of assembly inputs, so a multi-assembly product takes
|
|
46
|
+
* the same vocabulary the single-view one does — `['hg38', 'mm39']` as readily
|
|
47
|
+
* as two hand-written configs.
|
|
48
|
+
*
|
|
49
|
+
* A hub brings a track catalog and a search index that names hits by those
|
|
50
|
+
* tracks' ids, so both come back beside the assemblies. Pass the host's own
|
|
51
|
+
* `tracks` and `aggregateTextSearchAdapters` as `host` and they come back merged
|
|
52
|
+
* in, the host's winning an id, which makes spreading the result into
|
|
53
|
+
* `createApp`'s options correct:
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* createApp(el, { ...options, ...(await resolveAssemblies(names, options)) })
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* A key neither the hubs nor the host supplied is left out rather than set to
|
|
60
|
+
* `undefined`, so a spread never clears one.
|
|
61
|
+
*
|
|
62
|
+
* Exported because the resolution is async and `createApp` is not: an engine
|
|
63
|
+
* that resolved its own genomes would have to hand back a controller whose
|
|
64
|
+
* `viewState` is undefined until the fetches land. A host builds its options
|
|
65
|
+
* asynchronously already (runtime plugins), so this belongs in that step.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveAssemblies<Track extends object = HubTrackConfig, Index extends LooseSearchIndex = TextSearchAdapterConfig>(inputs: AssemblyInput[], host?: HostCatalog<Track, Index>): Promise<ResolvedAssemblies<Track, Index>>;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { expandLooseSearchIndex } from '@jbrowse/core/util/expandLooseSearchIndex';
|
|
2
|
+
import { fetchHub } from '@jbrowse/core/util/fetchHub';
|
|
3
|
+
import { isSequenceUri, makeAssembly } from '@jbrowse/core/util/makeAssembly';
|
|
4
|
+
import { searchIndexKey, withHostOverrides } from './controllerTracks.js';
|
|
5
|
+
// A hub is one assembly, so an index it writes short is on that assembly: it is
|
|
6
|
+
// expanded here, before the merge puts it beside other hubs' assemblies where no
|
|
7
|
+
// single one would fill it. What still names no adapter type cannot be
|
|
8
|
+
// constructed, so it is dropped rather than cast.
|
|
9
|
+
function searchAdaptersOf(hub) {
|
|
10
|
+
const name = hub.assemblies?.[0]?.name;
|
|
11
|
+
const found = (hub.aggregateTextSearchAdapters ?? [])
|
|
12
|
+
.map(index => expandLooseSearchIndex(index, typeof name === 'string' ? [name] : []))
|
|
13
|
+
.filter((a) => typeof a?.type === 'string');
|
|
14
|
+
return found.length ? found : undefined;
|
|
15
|
+
}
|
|
16
|
+
function tracksOf(hub) {
|
|
17
|
+
const found = (hub.tracks ?? []).filter((t) => typeof t.trackId === 'string');
|
|
18
|
+
return found.length ? found : undefined;
|
|
19
|
+
}
|
|
20
|
+
function fromHubConfig(hub) {
|
|
21
|
+
const assembly = hub.assemblies?.[0];
|
|
22
|
+
if (!assembly) {
|
|
23
|
+
throw new Error('hub config has no assemblies');
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
assembly,
|
|
27
|
+
aggregateTextSearchAdapters: searchAdaptersOf(hub),
|
|
28
|
+
tracks: tracksOf(hub),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** One assembly in any of the four accepted shapes. */
|
|
32
|
+
export async function resolveAssembly(input) {
|
|
33
|
+
if (typeof input === 'string') {
|
|
34
|
+
return isSequenceUri(input)
|
|
35
|
+
? { assembly: makeAssembly({ fastaUri: input }) }
|
|
36
|
+
: fromHubConfig(await fetchHub(input));
|
|
37
|
+
}
|
|
38
|
+
else if ('assemblies' in input) {
|
|
39
|
+
return fromHubConfig(input);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return { assembly: input };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a whole list of assembly inputs, so a multi-assembly product takes
|
|
47
|
+
* the same vocabulary the single-view one does — `['hg38', 'mm39']` as readily
|
|
48
|
+
* as two hand-written configs.
|
|
49
|
+
*
|
|
50
|
+
* A hub brings a track catalog and a search index that names hits by those
|
|
51
|
+
* tracks' ids, so both come back beside the assemblies. Pass the host's own
|
|
52
|
+
* `tracks` and `aggregateTextSearchAdapters` as `host` and they come back merged
|
|
53
|
+
* in, the host's winning an id, which makes spreading the result into
|
|
54
|
+
* `createApp`'s options correct:
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* createApp(el, { ...options, ...(await resolveAssemblies(names, options)) })
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* A key neither the hubs nor the host supplied is left out rather than set to
|
|
61
|
+
* `undefined`, so a spread never clears one.
|
|
62
|
+
*
|
|
63
|
+
* Exported because the resolution is async and `createApp` is not: an engine
|
|
64
|
+
* that resolved its own genomes would have to hand back a controller whose
|
|
65
|
+
* `viewState` is undefined until the fetches land. A host builds its options
|
|
66
|
+
* asynchronously already (runtime plugins), so this belongs in that step.
|
|
67
|
+
*/
|
|
68
|
+
export async function resolveAssemblies(inputs, host = {}) {
|
|
69
|
+
const resolved = await Promise.all(inputs.map(resolveAssembly));
|
|
70
|
+
const tracks = withHostOverrides(resolved.flatMap(r => r.tracks ?? []), host.tracks, 'trackId');
|
|
71
|
+
const adapters = withHostOverrides(resolved.flatMap(r => r.aggregateTextSearchAdapters ?? []), host.aggregateTextSearchAdapters, searchIndexKey);
|
|
72
|
+
return {
|
|
73
|
+
assemblies: resolved.map(r => r.assembly),
|
|
74
|
+
...(tracks.length ? { tracks } : {}),
|
|
75
|
+
...(adapters.length ? { aggregateTextSearchAdapters: adapters } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
package/esm/rpcWorker.d.ts
CHANGED
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
import { RpcServer } from '@jbrowse/core/util/librpc';
|
|
2
2
|
import type { PluginConstructor } from '@jbrowse/core/Plugin';
|
|
3
|
-
import type { LoadedPlugin } from '@jbrowse/core/PluginLoader';
|
|
3
|
+
import type { LoadedPlugin, ReExportRegistryLoader } from '@jbrowse/core/PluginLoader';
|
|
4
4
|
declare global {
|
|
5
5
|
interface Window {
|
|
6
6
|
rpcServer?: RpcServer;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export interface WorkerOptions {
|
|
10
10
|
fetchESM?: (url: string) => Promise<LoadedPlugin>;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The product's `workerReExports.generated.ts`, so a runtime plugin loading
|
|
13
|
+
* here reads the host's copy of every `@jbrowse` package the product
|
|
14
|
+
* bundles.
|
|
15
|
+
*/
|
|
16
|
+
reExports: ReExportRegistryLoader;
|
|
17
|
+
}
|
|
18
|
+
type RpcFunc = (args: unknown) => Promise<unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Wire the method's `statusCallback` to the status channel the driver opened
|
|
21
|
+
* for this call — and to nothing at all when it opened none.
|
|
22
|
+
*
|
|
23
|
+
* The `channel` is the driver's statement of whether anyone is listening
|
|
24
|
+
* (`WebWorkerHandle.call`), so a method run without one has to see the
|
|
25
|
+
* `undefined` its caller passed. Injecting a callback regardless made every
|
|
26
|
+
* "no statusCallback" branch in the worker unreachable: the byte reporter an
|
|
27
|
+
* adapter hands its reader, the emit `createProgressReporter` gates, and the
|
|
28
|
+
* postMessage each of those cost to reach a main-thread listener that dropped
|
|
29
|
+
* it.
|
|
30
|
+
*
|
|
31
|
+
* `channel` is transport bookkeeping and comes back OFF here, the mirror of
|
|
32
|
+
* `BaseRpcDriver.call` stripping `statusCallback` on the way out. It used to
|
|
33
|
+
* ride through into every worker method's arguments, where nothing read it and
|
|
34
|
+
* `RpcExecuteArgs<M>` did not declare it — a key the main-thread driver never
|
|
35
|
+
* adds, so the two drivers handed `execute` different bags. `rpcDriverParity`
|
|
36
|
+
* is what says so.
|
|
37
|
+
*
|
|
38
|
+
* Exported for its test and not from the package barrel: `initializeWorker` is
|
|
39
|
+
* the only thing a product's worker entry calls, and the branch here is a claim
|
|
40
|
+
* about the driver on the other side of postMessage that nothing else asserts.
|
|
41
|
+
*/
|
|
42
|
+
export declare function wrapForRpc(func: RpcFunc): (args: unknown) => Promise<unknown>;
|
|
43
|
+
export declare function initializeWorker(corePlugins: PluginConstructor[], opts: WorkerOptions): Promise<void>;
|
|
44
|
+
export {};
|
package/esm/rpcWorker.js
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import PluginLoader from '@jbrowse/core/PluginLoader';
|
|
1
|
+
import PluginLoader, { preloadUMDBundles } from '@jbrowse/core/PluginLoader';
|
|
2
2
|
import PluginManager from '@jbrowse/core/PluginManager';
|
|
3
|
+
import { setNumberGrouping, throttleStatusEmits } from '@jbrowse/core/util';
|
|
3
4
|
import { RpcServer, serializeError } from '@jbrowse/core/util/librpc';
|
|
4
|
-
|
|
5
|
+
import { setStackTraceLimit } from '@jbrowse/core/util/setStackTraceLimit';
|
|
6
|
+
import { parallelizeWorkerChunkLoading } from './parallelChunkLoading.js';
|
|
7
|
+
/**
|
|
8
|
+
* Start downloading what a boot configuration naming these plugins will load.
|
|
9
|
+
* A page that started this worker early sends the hint once it has resolved
|
|
10
|
+
* its plugins, which is well before it can send the configuration: that waits
|
|
11
|
+
* for the page to evaluate them and build its session. The configuration is
|
|
12
|
+
* still what the worker loads.
|
|
13
|
+
*/
|
|
14
|
+
function preloadHintedPlugins({ plugins, windowHref }, opts) {
|
|
15
|
+
if (plugins.length) {
|
|
16
|
+
opts.reExports().catch(() => { });
|
|
17
|
+
preloadUMDBundles(plugins, windowHref);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// waits for a message from the main thread containing our configuration, which
|
|
21
|
+
// must be sent on boot
|
|
22
|
+
function receiveConfiguration(opts) {
|
|
5
23
|
const configurationP = new Promise(resolve => {
|
|
6
24
|
function listener(e) {
|
|
7
|
-
if (e.data.message === 'config') {
|
|
25
|
+
if (e.data.message === 'config' && e.data.config) {
|
|
8
26
|
resolve(e.data.config);
|
|
9
27
|
removeEventListener('message', listener);
|
|
10
28
|
}
|
|
29
|
+
else if (e.data.message === 'preloadPlugins' && e.data.hint) {
|
|
30
|
+
preloadHintedPlugins(e.data.hint, opts);
|
|
31
|
+
}
|
|
11
32
|
}
|
|
12
33
|
self.addEventListener('message', listener);
|
|
13
34
|
});
|
|
@@ -15,43 +36,128 @@ function receiveConfiguration() {
|
|
|
15
36
|
return configurationP;
|
|
16
37
|
}
|
|
17
38
|
async function getPluginManager(corePlugins, opts) {
|
|
18
|
-
const config = await receiveConfiguration();
|
|
19
|
-
|
|
39
|
+
const config = await receiveConfiguration(opts);
|
|
40
|
+
// this realm formats its own strings — a jexl `mouseover` slot renders a
|
|
41
|
+
// tooltip against the full feature here rather than shipping it back — so it
|
|
42
|
+
// needs the main thread's display preference before any RPC method runs
|
|
43
|
+
setNumberGrouping(config.numberGrouping);
|
|
44
|
+
// Keep each runtime plugin's `definition` on its load record (mirroring the
|
|
45
|
+
// main thread's createPluginManager) so PluginManager populates
|
|
46
|
+
// runtimePluginDefinitions. A plugin that resolves a sibling asset from its
|
|
47
|
+
// own bundle url in the worker — e.g. GraphGenomeView's Bandage layout
|
|
48
|
+
// engine — needs its definition here, not just its instance.
|
|
49
|
+
const runtimePlugins = config.plugins.length
|
|
50
|
+
? await new PluginLoader(config.plugins, opts)
|
|
51
|
+
.installGlobalReExports(self, opts.reExports)
|
|
52
|
+
.load(config.windowHref)
|
|
53
|
+
: [];
|
|
20
54
|
return new PluginManager([
|
|
21
|
-
...corePlugins.map(
|
|
22
|
-
...(
|
|
23
|
-
|
|
55
|
+
...corePlugins.map(P => ({ plugin: new P() })),
|
|
56
|
+
...runtimePlugins.map(({ plugin: P, definition }) => ({
|
|
57
|
+
plugin: new P(),
|
|
58
|
+
definition,
|
|
59
|
+
})),
|
|
60
|
+
])
|
|
24
61
|
.createPluggableElements()
|
|
25
62
|
.configure();
|
|
26
63
|
}
|
|
27
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Wire the method's `statusCallback` to the status channel the driver opened
|
|
66
|
+
* for this call — and to nothing at all when it opened none.
|
|
67
|
+
*
|
|
68
|
+
* The `channel` is the driver's statement of whether anyone is listening
|
|
69
|
+
* (`WebWorkerHandle.call`), so a method run without one has to see the
|
|
70
|
+
* `undefined` its caller passed. Injecting a callback regardless made every
|
|
71
|
+
* "no statusCallback" branch in the worker unreachable: the byte reporter an
|
|
72
|
+
* adapter hands its reader, the emit `createProgressReporter` gates, and the
|
|
73
|
+
* postMessage each of those cost to reach a main-thread listener that dropped
|
|
74
|
+
* it.
|
|
75
|
+
*
|
|
76
|
+
* `channel` is transport bookkeeping and comes back OFF here, the mirror of
|
|
77
|
+
* `BaseRpcDriver.call` stripping `statusCallback` on the way out. It used to
|
|
78
|
+
* ride through into every worker method's arguments, where nothing read it and
|
|
79
|
+
* `RpcExecuteArgs<M>` did not declare it — a key the main-thread driver never
|
|
80
|
+
* adds, so the two drivers handed `execute` different bags. `rpcDriverParity`
|
|
81
|
+
* is what says so.
|
|
82
|
+
*
|
|
83
|
+
* Exported for its test and not from the package barrel: `initializeWorker` is
|
|
84
|
+
* the only thing a product's worker entry calls, and the branch here is a claim
|
|
85
|
+
* about the driver on the other side of postMessage that nothing else asserts.
|
|
86
|
+
*/
|
|
87
|
+
export function wrapForRpc(func) {
|
|
28
88
|
return (args) => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
89
|
+
const { channel, ...rest } = args;
|
|
90
|
+
return func(channel === undefined
|
|
91
|
+
? rest
|
|
92
|
+
: {
|
|
93
|
+
...rest,
|
|
94
|
+
// thinned here rather than only where it lands: every emit costs a
|
|
95
|
+
// postMessage, a task and a clone on the main thread, which then
|
|
96
|
+
// throttles the same stream again to decide what to draw
|
|
97
|
+
statusCallback: throttleStatusEmits((message) => {
|
|
98
|
+
self.rpcServer?.emit(channel, message);
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
37
101
|
};
|
|
38
102
|
}
|
|
39
103
|
export async function initializeWorker(corePlugins, opts) {
|
|
104
|
+
// a worker error's stack is captured here, not on the main thread, so the
|
|
105
|
+
// frame limit has to be raised in this scope to deepen it
|
|
106
|
+
setStackTraceLimit();
|
|
107
|
+
parallelizeWorkerChunkLoading();
|
|
40
108
|
self.addEventListener('error', event => {
|
|
41
109
|
console.error('[Worker uncaught error]', event.error || event.message);
|
|
42
110
|
});
|
|
43
111
|
self.addEventListener('unhandledrejection', event => {
|
|
44
112
|
console.error('[Worker unhandled rejection]', event.reason);
|
|
45
113
|
});
|
|
114
|
+
// the page posts calls without waiting for 'ready', and only the server
|
|
115
|
+
// built once the plugins have loaded can answer them
|
|
116
|
+
const early = [];
|
|
117
|
+
const hold = (e) => {
|
|
118
|
+
if (e.data.libRpc) {
|
|
119
|
+
early.push(e);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
self.addEventListener('message', hold);
|
|
46
123
|
try {
|
|
47
124
|
const pluginManager = await getPluginManager(corePlugins, opts);
|
|
48
125
|
const rpcConfig = Object.fromEntries(pluginManager
|
|
49
126
|
.getRpcElements()
|
|
50
|
-
|
|
51
|
-
|
|
127
|
+
// `invoke`, not `execute`: it deserializes the arguments before handing
|
|
128
|
+
// them on, which is what makes the blob map current and the declared
|
|
129
|
+
// arg types true. MainThreadRpcDriver binds the same one.
|
|
130
|
+
.map(e => [e.name, wrapForRpc(e.invoke.bind(e))]));
|
|
131
|
+
const server = new RpcServer(rpcConfig);
|
|
132
|
+
self.rpcServer = server;
|
|
133
|
+
self.removeEventListener('message', hold);
|
|
134
|
+
for (const e of early) {
|
|
135
|
+
server.handler(e);
|
|
136
|
+
}
|
|
52
137
|
postMessage({ message: 'ready' });
|
|
53
138
|
}
|
|
54
139
|
catch (e) {
|
|
55
|
-
|
|
140
|
+
// The boot handshake's last frame, and it must never fail to send — for the
|
|
141
|
+
// reason RpcServer.throw must not, one level worse. `serializeError` copies
|
|
142
|
+
// an error's own-enumerable properties and skips only the ones that are
|
|
143
|
+
// themselves functions, so an error carrying an object with a method on it
|
|
144
|
+
// (a `cause` that is a Response, an adapter instance) makes this postMessage
|
|
145
|
+
// throw DataCloneError. This runs floating, so that throw becomes an
|
|
146
|
+
// unhandled rejection the listener above only logs: no 'ready', no 'error',
|
|
147
|
+
// and no ErrorEvent either, since a rejected promise is not a script error.
|
|
148
|
+
// The boot promise in WebWorkerRpcDriver then never settles, and because
|
|
149
|
+
// `invalidate` hangs off its rejection, the pool hands every later caller
|
|
150
|
+
// the same pending promise — every RPC on that slot awaits forever, showing
|
|
151
|
+
// a spinner and no error, until the page is reloaded. One worker's failure
|
|
152
|
+
// to report a failure takes the whole session's worth of calls with it.
|
|
153
|
+
//
|
|
154
|
+
// deserializeError accepts a bare string, which is what makes the fallback
|
|
155
|
+
// sound: a string always clones.
|
|
156
|
+
try {
|
|
157
|
+
postMessage({ message: 'error', error: serializeError(e) });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
postMessage({ message: 'error', error: `${e}` });
|
|
161
|
+
}
|
|
56
162
|
}
|
|
57
163
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
+
/**
|
|
3
|
+
* Every display-instance key an old session may carry and have honoured, by
|
|
4
|
+
* display type, `*` for any display, so `jbrowse validate` reports one as
|
|
5
|
+
* stale rather than dead.
|
|
6
|
+
*/
|
|
7
|
+
export declare function migratedDisplayInstanceKeys(pluginManager: PluginManager): Record<string, string[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the snapshot by identity when nothing in it is retired, which is
|
|
10
|
+
* every session this build wrote.
|
|
11
|
+
*
|
|
12
|
+
* An app session holds `views`; a single-view embedded product's session holds
|
|
13
|
+
* one `view`, and a share link written by either reaches both.
|
|
14
|
+
*/
|
|
15
|
+
export declare function migrateSessionSnapshot(snapshot: Record<string, unknown>, pluginManager: PluginManager): Record<string, unknown>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { getConfigurationSchemaMetadata, liftRetiredSpellings, } from '@jbrowse/core/configuration';
|
|
2
|
+
function resolverFor(pluginManager) {
|
|
3
|
+
const byName = new Map();
|
|
4
|
+
for (const display of pluginManager.getDisplayElements()) {
|
|
5
|
+
byName.set(display.name, { display });
|
|
6
|
+
for (const retired of display.retiredTypes) {
|
|
7
|
+
byName.set(retired.type, { display, retired });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return type => (typeof type === 'string' ? byName.get(type) : undefined);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Every display-instance key an old session may carry and have honoured, by
|
|
14
|
+
* display type, `*` for any display, so `jbrowse validate` reports one as
|
|
15
|
+
* stale rather than dead.
|
|
16
|
+
*/
|
|
17
|
+
export function migratedDisplayInstanceKeys(pluginManager) {
|
|
18
|
+
return {
|
|
19
|
+
'*': ['heightPreConfig'],
|
|
20
|
+
...Object.fromEntries(pluginManager
|
|
21
|
+
.getDisplayElements()
|
|
22
|
+
.flatMap(d => d.retiredState ? [[d.name, [...d.retiredState.keys]]] : [])),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function isObject(v) {
|
|
26
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
27
|
+
}
|
|
28
|
+
function migrateDisplay(display, trackConfigId, resolve, collected) {
|
|
29
|
+
const resolved = resolve(display.type);
|
|
30
|
+
if (!resolved) {
|
|
31
|
+
return display;
|
|
32
|
+
}
|
|
33
|
+
const { display: displayType, retired } = resolved;
|
|
34
|
+
const state = displayType.retiredState;
|
|
35
|
+
const shed = ['heightPreConfig', ...(state?.keys ?? [])].filter(k => k in display);
|
|
36
|
+
if (!retired && shed.length === 0) {
|
|
37
|
+
return display;
|
|
38
|
+
}
|
|
39
|
+
const { configuration } = display;
|
|
40
|
+
if (typeof configuration !== 'string') {
|
|
41
|
+
return { ...display, type: displayType.name };
|
|
42
|
+
}
|
|
43
|
+
const lifted = {
|
|
44
|
+
...state?.lift(display),
|
|
45
|
+
...(typeof display.heightPreConfig === 'number'
|
|
46
|
+
? { height: display.heightPreConfig }
|
|
47
|
+
: {}),
|
|
48
|
+
};
|
|
49
|
+
const migrated = retired?.migrate?.(lifted) ?? lifted;
|
|
50
|
+
const meta = getConfigurationSchemaMetadata(displayType.configSchema);
|
|
51
|
+
const settings = meta ? liftRetiredSpellings(meta, migrated) : migrated;
|
|
52
|
+
const displayId = retired && configuration === `${trackConfigId}-${retired.type}`
|
|
53
|
+
? `${trackConfigId}-${displayType.name}`
|
|
54
|
+
: configuration;
|
|
55
|
+
if (trackConfigId && Object.keys(settings).length > 0) {
|
|
56
|
+
collected.push({
|
|
57
|
+
trackConfigId,
|
|
58
|
+
displayId,
|
|
59
|
+
displayType: displayType.name,
|
|
60
|
+
settings,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const rest = { ...display };
|
|
64
|
+
for (const key of shed) {
|
|
65
|
+
delete rest[key];
|
|
66
|
+
}
|
|
67
|
+
return { ...rest, type: displayType.name, configuration: displayId };
|
|
68
|
+
}
|
|
69
|
+
function migrateTrack(track, resolve, collected) {
|
|
70
|
+
if (!isObject(track) || !Array.isArray(track.displays)) {
|
|
71
|
+
return track;
|
|
72
|
+
}
|
|
73
|
+
const trackConfigId = typeof track.configuration === 'string' ? track.configuration : undefined;
|
|
74
|
+
const displays = track.displays;
|
|
75
|
+
const next = displays.map(d => isObject(d) ? migrateDisplay(d, trackConfigId, resolve, collected) : d);
|
|
76
|
+
return next.some((d, i) => d !== displays[i])
|
|
77
|
+
? { ...track, displays: next }
|
|
78
|
+
: track;
|
|
79
|
+
}
|
|
80
|
+
function migrateView(view, resolve, collected) {
|
|
81
|
+
let result = view;
|
|
82
|
+
const { tracks, views } = view;
|
|
83
|
+
if (Array.isArray(tracks)) {
|
|
84
|
+
const next = tracks.map(t => migrateTrack(t, resolve, collected));
|
|
85
|
+
if (next.some((t, i) => t !== tracks[i])) {
|
|
86
|
+
result = { ...result, tracks: next };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// a synteny view's panels are views of their own
|
|
90
|
+
if (Array.isArray(views)) {
|
|
91
|
+
const next = views.map(v => isObject(v) ? migrateView(v, resolve, collected) : v);
|
|
92
|
+
if (next.some((v, i) => v !== views[i])) {
|
|
93
|
+
result = { ...result, views: next };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
// The entry the settings belong to is the one under their id, else the one
|
|
99
|
+
// whose type loads as the same display: a session track still spelling the
|
|
100
|
+
// retired type holds it under the retired type's id.
|
|
101
|
+
function mergeSettingsIntoTrackConfig(track, { displayId, displayType, settings }, resolve) {
|
|
102
|
+
const list = Array.isArray(track.displays)
|
|
103
|
+
? track.displays
|
|
104
|
+
: [];
|
|
105
|
+
const index = [
|
|
106
|
+
(d) => isObject(d) && d.displayId === displayId,
|
|
107
|
+
(d) => isObject(d) && resolve(d.type)?.display.name === displayType,
|
|
108
|
+
]
|
|
109
|
+
.map(matches => list.findIndex(matches))
|
|
110
|
+
.find(i => i !== -1);
|
|
111
|
+
const displays = index === undefined
|
|
112
|
+
? [...list, { type: displayType, displayId, ...settings }]
|
|
113
|
+
: list.map((d, i) => i === index ? { ...d, ...settings } : d);
|
|
114
|
+
return { ...track, displays };
|
|
115
|
+
}
|
|
116
|
+
function applyExtractedSettings(snapshot, collected, resolve) {
|
|
117
|
+
if (collected.length === 0) {
|
|
118
|
+
return snapshot;
|
|
119
|
+
}
|
|
120
|
+
const sessionTracks = Array.isArray(snapshot.sessionTracks)
|
|
121
|
+
? [...snapshot.sessionTracks]
|
|
122
|
+
: [];
|
|
123
|
+
const sessionTrackIndex = new Map(sessionTracks.map((t, i) => [isObject(t) ? t.trackId : undefined, i]));
|
|
124
|
+
const deltas = isObject(snapshot.trackConfigDeltas)
|
|
125
|
+
? { ...snapshot.trackConfigDeltas }
|
|
126
|
+
: {};
|
|
127
|
+
let sessionTracksChanged = false;
|
|
128
|
+
let deltasChanged = false;
|
|
129
|
+
for (const extracted of collected) {
|
|
130
|
+
const { trackConfigId } = extracted;
|
|
131
|
+
const index = sessionTrackIndex.get(trackConfigId);
|
|
132
|
+
const sessionTrack = index === undefined ? undefined : sessionTracks[index];
|
|
133
|
+
if (index !== undefined && isObject(sessionTrack)) {
|
|
134
|
+
sessionTracks[index] = mergeSettingsIntoTrackConfig(sessionTrack, extracted, resolve);
|
|
135
|
+
sessionTracksChanged = true;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const existing = deltas[trackConfigId];
|
|
139
|
+
deltas[trackConfigId] = mergeSettingsIntoTrackConfig(isObject(existing) ? existing : { trackId: trackConfigId }, extracted, resolve);
|
|
140
|
+
deltasChanged = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
...snapshot,
|
|
145
|
+
...(sessionTracksChanged ? { sessionTracks } : {}),
|
|
146
|
+
...(deltasChanged ? { trackConfigDeltas: deltas } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Returns the snapshot by identity when nothing in it is retired, which is
|
|
151
|
+
* every session this build wrote.
|
|
152
|
+
*
|
|
153
|
+
* An app session holds `views`; a single-view embedded product's session holds
|
|
154
|
+
* one `view`, and a share link written by either reaches both.
|
|
155
|
+
*/
|
|
156
|
+
export function migrateSessionSnapshot(snapshot, pluginManager) {
|
|
157
|
+
const { views, view } = snapshot;
|
|
158
|
+
const many = Array.isArray(views);
|
|
159
|
+
if (!many && !isObject(view)) {
|
|
160
|
+
return snapshot;
|
|
161
|
+
}
|
|
162
|
+
const resolve = resolverFor(pluginManager);
|
|
163
|
+
const collected = [];
|
|
164
|
+
let result = snapshot;
|
|
165
|
+
if (many) {
|
|
166
|
+
const next = views.map(v => isObject(v) ? migrateView(v, resolve, collected) : v);
|
|
167
|
+
if (next.some((v, i) => v !== views[i])) {
|
|
168
|
+
result = { ...result, views: next };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (isObject(view)) {
|
|
172
|
+
const next = migrateView(view, resolve, collected);
|
|
173
|
+
if (next !== view) {
|
|
174
|
+
result = { ...result, view: next };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return applyExtractedSettings(result, collected, resolve);
|
|
178
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AbstractSessionModel } from '@jbrowse/core/util';
|
|
2
|
+
/**
|
|
3
|
+
* A serialized session. Open-shaped on purpose: the concrete shape is the
|
|
4
|
+
* session model's `SnapshotIn`, which grows with every plugin a host loads.
|
|
5
|
+
*/
|
|
6
|
+
export interface SessionSnapshot {
|
|
7
|
+
name: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Serialize a live session into a compact, URL-safe string for a query param or
|
|
12
|
+
* hash fragment. Deflated then base64url-encoded, so it survives a URL intact
|
|
13
|
+
* and stays far smaller than raw JSON.
|
|
14
|
+
*
|
|
15
|
+
* Not a plain `getSnapshot`: a workspace arrangement turned on by the sender's
|
|
16
|
+
* preference is not in the snapshot, so a raw one renders as a classic stack
|
|
17
|
+
* for whoever opens the link. `getShareableSessionSnapshot` stamps it first.
|
|
18
|
+
*
|
|
19
|
+
* The session travels but the config does not — the receiving app supplies its
|
|
20
|
+
* own assembly/tracks. Put the result somewhere the server never sees (the hash
|
|
21
|
+
* fragment) if it may be long: a query string can exceed the request-line limit
|
|
22
|
+
* and get a 414, which is why jbrowse-web moved its own there.
|
|
23
|
+
*
|
|
24
|
+
* Carries `ENCODED_PREFIX`, the same `?session=encoded-…` value jbrowse-web's
|
|
25
|
+
* SessionLoader decodes, so a link built by an embedded product opens in
|
|
26
|
+
* jbrowse.org/jb2 and vice versa. The format is `encodeSessionParam`'s `long`
|
|
27
|
+
* mode; sessionUrl.test.ts asserts the two stay byte-identical.
|
|
28
|
+
*/
|
|
29
|
+
export declare function encodeSessionToUrl(session: AbstractSessionModel): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Inverse of {@link encodeSessionToUrl}, and of the `json-` form jbrowse-web's
|
|
32
|
+
* share dialog writes. Accepts an `encoded-` value with or without its prefix,
|
|
33
|
+
* so a raw `?session=` value from either app passes straight through.
|
|
34
|
+
*
|
|
35
|
+
* Throws on anything that isn't a decodable session — a truncated link, or a
|
|
36
|
+
* `share-`/`spec-` param an embedded product doesn't handle — so a host can
|
|
37
|
+
* fall back to its own declarative launch state instead of opening a
|
|
38
|
+
* half-built session.
|
|
39
|
+
*/
|
|
40
|
+
export declare function decodeSessionFromUrl(value: string): Promise<SessionSnapshot>;
|