@jbrowse/product-core 4.2.1 → 5.0.0-beta.1

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.
Files changed (133) hide show
  1. package/esm/RootModel/BaseRootModel.d.ts +629 -304
  2. package/esm/RootModel/BaseRootModel.js +138 -14
  3. package/esm/RootModel/HierarchicalConfig.d.ts +57 -21
  4. package/esm/RootModel/HierarchicalConfig.js +36 -0
  5. package/esm/RootModel/InternetAccounts.d.ts +18 -1
  6. package/esm/RootModel/InternetAccounts.js +120 -22
  7. package/esm/RootModel/PreferencesConfig.d.ts +53 -0
  8. package/esm/RootModel/PreferencesConfig.js +61 -0
  9. package/esm/RootModel/createConfigModel.d.ts +232 -0
  10. package/esm/RootModel/createConfigModel.js +56 -0
  11. package/esm/RootModel/index.d.ts +10 -5
  12. package/esm/RootModel/index.js +10 -5
  13. package/esm/RootModel/menuItems.d.ts +24 -0
  14. package/esm/RootModel/menuItems.js +143 -0
  15. package/esm/Session/BaseSession.d.ts +310 -11
  16. package/esm/Session/BaseSession.js +448 -14
  17. package/esm/Session/Connections.d.ts +114 -8
  18. package/esm/Session/Connections.js +195 -74
  19. package/esm/Session/DrawerWidgets.d.ts +115 -12
  20. package/esm/Session/DrawerWidgets.js +161 -42
  21. package/esm/Session/MultipleViews.d.ts +1266 -1128
  22. package/esm/Session/MultipleViews.js +357 -66
  23. package/esm/Session/Preferences.d.ts +1665 -0
  24. package/esm/Session/Preferences.js +54 -0
  25. package/esm/Session/ReferenceManagement.d.ts +28 -3
  26. package/esm/Session/ReferenceManagement.js +56 -41
  27. package/esm/Session/SessionTracks.d.ts +1275 -1138
  28. package/esm/Session/SessionTracks.js +532 -35
  29. package/esm/Session/Themes.d.ts +87 -2
  30. package/esm/Session/Themes.js +156 -23
  31. package/esm/Session/TrackMenu.d.ts +112 -0
  32. package/esm/Session/TrackMenu.js +200 -0
  33. package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
  34. package/esm/Session/TrackMenuSessionMixin.js +41 -0
  35. package/esm/Session/Tracks.d.ts +1169 -1117
  36. package/esm/Session/Tracks.js +160 -20
  37. package/esm/Session/assertSessionModel.d.ts +21 -0
  38. package/esm/Session/assertSessionModel.js +1 -0
  39. package/esm/Session/finalizeSession.d.ts +16 -0
  40. package/esm/Session/finalizeSession.js +29 -0
  41. package/esm/Session/index.d.ts +22 -10
  42. package/esm/Session/index.js +13 -10
  43. package/esm/Session/shareableSnapshot.d.ts +77 -0
  44. package/esm/Session/shareableSnapshot.js +196 -0
  45. package/esm/Session/temporaryAssemblyTracks.d.ts +23 -0
  46. package/esm/Session/temporaryAssemblyTracks.js +38 -0
  47. package/esm/assertExtends.d.ts +55 -0
  48. package/esm/assertExtends.js +1 -0
  49. package/esm/controllerTracks.d.ts +56 -0
  50. package/esm/controllerTracks.js +77 -0
  51. package/esm/destroyViewState.d.ts +21 -0
  52. package/esm/destroyViewState.js +29 -0
  53. package/esm/index.d.ts +31 -4
  54. package/esm/index.js +31 -4
  55. package/esm/loadPlugins.d.ts +33 -0
  56. package/esm/loadPlugins.js +38 -0
  57. package/esm/localFiles.d.ts +74 -0
  58. package/esm/localFiles.js +156 -0
  59. package/esm/observeSession.d.ts +56 -0
  60. package/esm/observeSession.js +91 -0
  61. package/esm/pluginInput.d.ts +32 -0
  62. package/esm/pluginInput.js +19 -0
  63. package/esm/pruneUnbuildableNodes.d.ts +109 -0
  64. package/esm/pruneUnbuildableNodes.js +568 -0
  65. package/esm/resolveAssemblies.d.ts +44 -0
  66. package/esm/resolveAssemblies.js +55 -0
  67. package/esm/rpcWorker.d.ts +26 -0
  68. package/esm/rpcWorker.js +96 -15
  69. package/esm/scheduleDetachedDestroy.d.ts +18 -0
  70. package/esm/scheduleDetachedDestroy.js +24 -0
  71. package/esm/sessionMigrations/index.d.ts +53 -0
  72. package/esm/sessionMigrations/index.js +432 -0
  73. package/esm/sessionUrl.d.ts +41 -0
  74. package/esm/sessionUrl.js +53 -0
  75. package/esm/sessionUtils.d.ts +69 -0
  76. package/esm/sessionUtils.js +613 -0
  77. package/esm/siblingCast.d.ts +14 -0
  78. package/esm/siblingCast.js +16 -0
  79. package/esm/snapshotUtils.d.ts +8 -0
  80. package/esm/snapshotUtils.js +12 -0
  81. package/esm/ui/AboutDialog.d.ts +3 -6
  82. package/esm/ui/AboutDialog.js +4 -4
  83. package/esm/ui/AboutDialogContents.d.ts +2 -6
  84. package/esm/ui/AboutDialogContents.js +34 -13
  85. package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
  86. package/esm/ui/AssemblyInfoPanel.js +69 -0
  87. package/esm/ui/DisplayDefaultsSection.d.ts +36 -0
  88. package/esm/ui/DisplayDefaultsSection.js +65 -0
  89. package/esm/ui/Drawer.d.ts +21 -0
  90. package/esm/ui/Drawer.js +60 -0
  91. package/esm/ui/DrawerControls.d.ts +5 -0
  92. package/esm/ui/DrawerControls.js +28 -0
  93. package/esm/ui/DrawerHeader.d.ts +6 -0
  94. package/esm/ui/DrawerHeader.js +36 -0
  95. package/esm/ui/DrawerWidget.d.ts +5 -0
  96. package/esm/ui/DrawerWidget.js +12 -0
  97. package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
  98. package/esm/ui/DrawerWidgetSelector.js +40 -0
  99. package/esm/ui/FileInfoPanel.d.ts +3 -6
  100. package/esm/ui/FileInfoPanel.js +35 -26
  101. package/esm/ui/HeaderButtons.d.ts +7 -1
  102. package/esm/ui/HeaderButtons.js +23 -18
  103. package/esm/ui/ModalWidget.d.ts +12 -0
  104. package/esm/ui/ModalWidget.js +33 -0
  105. package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
  106. package/esm/ui/ModalWidgetAppBar.js +9 -0
  107. package/esm/ui/PreferencesDialog.d.ts +56 -0
  108. package/esm/ui/PreferencesDialog.js +145 -0
  109. package/esm/ui/PreferencesResetDialog.d.ts +8 -0
  110. package/esm/ui/PreferencesResetDialog.js +19 -0
  111. package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
  112. package/esm/ui/RefNameAliasesDialog.js +65 -0
  113. package/esm/ui/RefNameInfoDialog.d.ts +3 -6
  114. package/esm/ui/RefNameInfoDialog.js +57 -63
  115. package/esm/ui/WidgetBody.d.ts +18 -0
  116. package/esm/ui/WidgetBody.js +22 -0
  117. package/esm/ui/WidgetHeading.d.ts +7 -0
  118. package/esm/ui/WidgetHeading.js +7 -0
  119. package/esm/ui/drawerLayout.d.ts +18 -0
  120. package/esm/ui/drawerLayout.js +22 -0
  121. package/esm/ui/index.d.ts +13 -1
  122. package/esm/ui/index.js +12 -1
  123. package/esm/ui/util.d.ts +37 -4
  124. package/esm/ui/util.js +35 -38
  125. package/esm/useEngineLifecycle.d.ts +13 -0
  126. package/esm/useEngineLifecycle.js +22 -0
  127. package/package.json +11 -15
  128. package/esm/RootModel/FormatAbout.d.ts +0 -12
  129. package/esm/RootModel/FormatAbout.js +0 -15
  130. package/esm/RootModel/FormatDetails.d.ts +0 -24
  131. package/esm/RootModel/FormatDetails.js +0 -27
  132. package/esm/Session/DialogQueue.d.ts +0 -17
  133. package/esm/Session/DialogQueue.js +0 -31
@@ -0,0 +1,156 @@
1
+ import { storeBlobLocation } from '@jbrowse/core/util';
2
+ /**
3
+ * Register in-memory bytes as browser-local files JBrowse can read, returning
4
+ * the `name -> BlobLocation` map {@link resolveLocalFileUris} substitutes with.
5
+ *
6
+ * This is the escape from "the data has to be on a web server first". A
7
+ * `BlobLocation` opens as a `BlobFile`, which serves byte ranges out of
8
+ * `Blob.slice()` — so a bgzipped+tabixed file, a BAM with its `.bai`, or a
9
+ * bigWig is *seeked into* exactly as if it were remote, and only the bytes the
10
+ * current view needs are ever touched. That is the whole point of doing this
11
+ * rather than inlining features into a config: a 200k-feature table is ~20MB of
12
+ * JSON to serialize, ship, and hold in memory, while the same data as a tabix
13
+ * file is a few MB the view reads a few KB of at a time.
14
+ *
15
+ * For hosts that hold their data in a process rather than at a URL — a notebook
16
+ * kernel, an R session, a desktop app — this is the way in, and it needs no web
17
+ * server, no CORS, and no temporary public bucket.
18
+ */
19
+ export function registerLocalFiles(files) {
20
+ return Object.fromEntries(Object.entries(files).map(([name, data]) => [name, register(name, data)]));
21
+ }
22
+ /**
23
+ * Fold newly-offered files into a controller's registered map, registering only
24
+ * the names it has not seen. Registering is what mints the blobIds, so a host
25
+ * re-stating its whole file set on every update — which is what a declarative
26
+ * host does — pays for each file once rather than once per update.
27
+ *
28
+ * Only ever adds: a name already registered keeps its existing blob, because a
29
+ * live track config points at that blobId and swapping it underneath would
30
+ * leave the track reading bytes nothing else references.
31
+ */
32
+ export function mergeLocalFiles(current, files) {
33
+ const fresh = Object.fromEntries(Object.entries(files).filter(([name]) => !current[name]));
34
+ return { ...current, ...registerLocalFiles(fresh) };
35
+ }
36
+ // Registering the same bytes twice used to mint a second blobId and leave the
37
+ // first in core's process-global blobMap forever. Nothing collects that map, so
38
+ // a host that hands the same `localFiles` to a new controller each time it
39
+ // rebuilds — which is how a host swaps genome or track set, now that the
40
+ // controller has no setters that rebuild internally — grew it without bound.
41
+ //
42
+ // Keyed on the caller's own object, which is the only identity available: bytes
43
+ // are not comparable cheaply, and two Uint8Arrays over the same buffer are
44
+ // legitimately different files. A WeakMap so a host dropping its reference to
45
+ // the data lets the entry go, and per name inside it because the same bytes can
46
+ // legitimately be registered under two names and a BlobLocation carries one.
47
+ const registered = new WeakMap();
48
+ function register(name, data) {
49
+ const byName = registered.get(data) ?? new Map();
50
+ const seen = byName.get(name);
51
+ if (seen) {
52
+ return seen;
53
+ }
54
+ const location = storeBlobLocation({
55
+ blob: data instanceof File ? data : new File([data], name),
56
+ });
57
+ byName.set(name, location);
58
+ registered.set(data, byName);
59
+ return location;
60
+ }
61
+ // A location node is `{ uri }` and at most the two keys that travel with it.
62
+ // Anything else carrying a `uri` is a shorthand adapter (`{ type, uri }`), and
63
+ // telling the two apart is what stops resolveLocalFileUris replacing a whole
64
+ // adapter with a bare location — see its comment.
65
+ const LOCATION_KEYS = new Set(['uri', 'baseUri', 'locationType']);
66
+ function isLocationNode(record) {
67
+ return Object.keys(record).every(key => LOCATION_KEYS.has(key));
68
+ }
69
+ /**
70
+ * Expand every shorthand adapter snapshot (`{ type, uri }`) in a config to the
71
+ * canonical location keys its config schema declares, using that adapter type's
72
+ * own `normalizeSnapshot` — the hook that exists so "downstream code can read
73
+ * location keys without knowing each shorthand".
74
+ *
75
+ * {@link resolveLocalFileUris} has to run on the expanded form, and this is the
76
+ * difference between `localFiles` working and silently doing nothing: the
77
+ * shorthand puts `uri` on the *adapter*, so substituting there would replace
78
+ * `{ type: 'BamAdapter', uri: 'x.bam' }` with a bare BlobLocation and take the
79
+ * adapter's type with it. Expanding first moves the name onto `bamLocation`,
80
+ * where it is a location node and substitution is what it looks like. The
81
+ * expansion also derives the conventional index sibling (`.bai`/`.tbi`/`.crai`)
82
+ * from the same string, so registering `x.bam` and `x.bam.bai` is enough and no
83
+ * caller has to know which adapter wanted which file.
84
+ *
85
+ * Idempotent, and a no-op for an already-canonical config or an adapter type
86
+ * that declares no shorthand.
87
+ */
88
+ export function normalizeAdapterSnapshots(config, pluginManager) {
89
+ if (Array.isArray(config)) {
90
+ return config.map(item => normalizeAdapterSnapshots(item, pluginManager));
91
+ }
92
+ if (typeof config === 'object' && config !== null) {
93
+ const record = config;
94
+ // `type` names a track, a display and an adapter alike, so this asks
95
+ // whether the name is an adapter rather than demanding that it is —
96
+ // getAdapterType throws on a miss, and 'AlignmentsTrack' is a miss
97
+ const normalize = typeof record.type === 'string' &&
98
+ pluginManager.hasAdapterType(record.type)
99
+ ? pluginManager.getAdapterType(record.type).normalizeSnapshot
100
+ : undefined;
101
+ const normalized = normalize?.(record) ?? record;
102
+ // Every normalizeSnapshot returns its argument unchanged when there was no
103
+ // shorthand to expand, so identity is the signal that `uri` was consumed —
104
+ // and it then has to GO. The same function runs again as the config
105
+ // schema's preProcessSnapshot when MST builds the tree, and a surviving
106
+ // `uri` makes that second pass rebuild the location from the string,
107
+ // overwriting the BlobLocation just substituted into it. That failure is
108
+ // invisible: the track ends up pointing at a relative URL named
109
+ // `volvox-sorted.bam`, which 404s against the host page's own origin.
110
+ const expanded = normalized === record
111
+ ? record
112
+ : Object.fromEntries(Object.entries(normalized).filter(([key]) => key !== 'uri'));
113
+ return Object.fromEntries(Object.entries(expanded).map(([key, value]) => [
114
+ key,
115
+ normalizeAdapterSnapshots(value, pluginManager),
116
+ ]));
117
+ }
118
+ return config;
119
+ }
120
+ /**
121
+ * Rewrite every `{ uri: <a registered name> }` in a track config to that file's
122
+ * `BlobLocation`, in place of nothing else — unregistered URIs are left alone,
123
+ * so a config can mix local and remote files freely.
124
+ *
125
+ * Runs on a config whose locations are already canonical, which is what
126
+ * `guessTrackConf` (for a loose track spec) and {@link normalizeAdapterSnapshots}
127
+ * (for a written-out one) each produce. So a host registers `peaks.bed.gz` and
128
+ * `peaks.bed.gz.tbi` under their plain names, the config names both under
129
+ * `bedGzLocation` and `index.location`, and this swaps both for blobs — index
130
+ * and all, with no host-side knowledge of which adapter wanted which sibling.
131
+ */
132
+ export function resolveLocalFileUris(config, locations) {
133
+ if (Array.isArray(config)) {
134
+ return config.map(item => resolveLocalFileUris(item, locations));
135
+ }
136
+ if (typeof config === 'object' && config !== null) {
137
+ const record = config;
138
+ const uri = record.uri;
139
+ // the whole location node is replaced: a BlobLocation carries blobId and
140
+ // its own locationType, and leaving the old uri alongside would make it
141
+ // fail isBlobLocation's discrimination. Only a *location* node, though — a
142
+ // shorthand adapter also has a `uri`, and replacing that node discards the
143
+ // adapter's type and every sibling key, which the config schema then papers
144
+ // over with its `/path/to/my.bam` default: a track that loads nothing, with
145
+ // nothing logged. normalizeAdapterSnapshots is what turns the shorthand
146
+ // into a location node so this substitutes it.
147
+ if (typeof uri === 'string' && locations[uri] && isLocationNode(record)) {
148
+ return locations[uri];
149
+ }
150
+ return Object.fromEntries(Object.entries(record).map(([key, value]) => [
151
+ key,
152
+ resolveLocalFileUris(value, locations),
153
+ ]));
154
+ }
155
+ return config;
156
+ }
@@ -0,0 +1,56 @@
1
+ import type { SessionSnapshot } from './sessionUrl.ts';
2
+ type OpenTracks = {
3
+ configuration: {
4
+ trackId: string;
5
+ };
6
+ }[];
7
+ interface ObservedView {
8
+ id?: string;
9
+ coarseVisibleLocStrings?: string;
10
+ views?: {
11
+ coarseVisibleLocStrings?: string;
12
+ }[];
13
+ tracks?: OpenTracks;
14
+ levels?: {
15
+ tracks: OpenTracks;
16
+ }[];
17
+ }
18
+ interface ObservedSession {
19
+ name: string;
20
+ views: ObservedView[];
21
+ selection?: unknown;
22
+ }
23
+ interface ObservedRoot {
24
+ session?: ObservedSession;
25
+ }
26
+ /** where one view is looking: a locstring, or one per panel for a comparative view */
27
+ export type ViewLocation = string | string[] | undefined;
28
+ export interface SessionObservers {
29
+ /** fires with every view's visible region when the layout settles */
30
+ onLocationChange?: (locations: ViewLocation[]) => void;
31
+ /** fires with the serialized feature when one is selected in any view */
32
+ onFeatureSelect?: (feature: unknown) => void;
33
+ /**
34
+ * fires with a plain-JSON snapshot of the layout the user built, in the shape
35
+ * the `session` option takes — so "save this arrangement" is storing this
36
+ * value and reopening it is passing it back
37
+ */
38
+ onSessionChange?: (session: SessionSnapshot) => void;
39
+ }
40
+ /**
41
+ * The live session as a plain JSON snapshot, ready to hand straight back as a
42
+ * product's `session` option (or its controller's `setSession`). The
43
+ * uncompressed twin of `encodeSession`, for hosts that move JSON rather than
44
+ * URLs — a notebook kernel, an R session, a "save this layout" button.
45
+ *
46
+ * Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't:
47
+ * display settings a user is *inheriting* from a promoted display-type default
48
+ * live in their own browser, never in the session, so a raw snapshot replays
49
+ * differently for whoever opens it.
50
+ */
51
+ export declare function getSessionSnapshot(session: ObservedSession): SessionSnapshot;
52
+ /**
53
+ * Wire the read-backs a host asked for, returning one disposer for all of them.
54
+ */
55
+ export declare function observeSession(root: ObservedRoot, { onLocationChange, onFeatureSelect, onSessionChange }: SessionObservers): () => void;
56
+ export {};
@@ -0,0 +1,91 @@
1
+ import { isFeature } from '@jbrowse/core/util';
2
+ import { autorun, untracked } from 'mobx';
3
+ import { getShareableSessionSnapshot } from './Session/shareableSnapshot.js';
4
+ /**
5
+ * The live session as a plain JSON snapshot, ready to hand straight back as a
6
+ * product's `session` option (or its controller's `setSession`). The
7
+ * uncompressed twin of `encodeSession`, for hosts that move JSON rather than
8
+ * URLs — a notebook kernel, an R session, a "save this layout" button.
9
+ *
10
+ * Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't:
11
+ * display settings a user is *inheriting* from a promoted display-type default
12
+ * live in their own browser, never in the session, so a raw snapshot replays
13
+ * differently for whoever opens it.
14
+ */
15
+ export function getSessionSnapshot(session) {
16
+ // `name` is restated rather than the whole thing cast: the bake returns an
17
+ // open record, but the model guarantees the session has one
18
+ return {
19
+ ...getShareableSessionSnapshot(session),
20
+ name: session.name,
21
+ };
22
+ }
23
+ // A linear view reports its visible region as one coarseVisibleLocStrings
24
+ // string; a comparative view (synteny/dotplot) has no single string, so report
25
+ // one entry per panel instead.
26
+ function viewLocation(view) {
27
+ return typeof view.coarseVisibleLocStrings === 'string'
28
+ ? view.coarseVisibleLocStrings || undefined
29
+ : view.views
30
+ ?.map(v => v.coarseVisibleLocStrings)
31
+ .filter(loc => loc !== undefined);
32
+ }
33
+ // Every trackId a view has open. A synteny view keeps its tracks per level
34
+ // rather than in one flat list, so reading `tracks` alone reports none of them —
35
+ // and the read-back then never fired when a synteny track was opened.
36
+ function viewTracks(view) {
37
+ return (view.tracks ?? view.levels?.flatMap(l => l.tracks) ?? [])
38
+ .map(t => t.configuration.trackId)
39
+ .join(',');
40
+ }
41
+ // The coarse signal the read-backs ride on: which views exist, what each has
42
+ // open, and where each is looking. Deliberately NOT the whole snapshot —
43
+ // depending on that would pull in offsetPx, and a snapshot is kilobytes, so a
44
+ // drag would push one per pointer event across the host's wire.
45
+ // coarseVisibleLocStrings is JBrowse's own debounced location, so this settles
46
+ // after a pan rather than firing during it.
47
+ //
48
+ // A plain function, not an action: actions run untracked, and computing this is
49
+ // precisely what subscribes the autorun below.
50
+ function layoutSignal(views) {
51
+ return views
52
+ .map(v => `${v.id}:${JSON.stringify(viewLocation(v))}:${viewTracks(v)}`)
53
+ .join('|');
54
+ }
55
+ /**
56
+ * Wire the read-backs a host asked for, returning one disposer for all of them.
57
+ */
58
+ export function observeSession(root, { onLocationChange, onFeatureSelect, onSessionChange }) {
59
+ const disposers = [];
60
+ // `session` is read inside each autorun and never captured: setSession
61
+ // replaces the whole node (`self.session = cast(...)` in BaseRootModel), so a
62
+ // captured one is a dead MST node the moment a session is restored, and the
63
+ // read-back would go silent for the rest of the product's life.
64
+ if (onLocationChange ?? onSessionChange) {
65
+ disposers.push(autorun(() => {
66
+ const { session } = root;
67
+ if (!session) {
68
+ return;
69
+ }
70
+ // the only dependency this autorun should have
71
+ layoutSignal(session.views);
72
+ onLocationChange?.(session.views.map(viewLocation));
73
+ // untracked, so reading the snapshot adds no dependencies of its own
74
+ // eslint-disable-next-line no-restricted-syntax -- effect input: the callback consumes the snapshot, the views are the trigger
75
+ onSessionChange?.(untracked(() => getSessionSnapshot(session)));
76
+ }));
77
+ }
78
+ if (onFeatureSelect) {
79
+ disposers.push(autorun(() => {
80
+ const selection = root.session?.selection;
81
+ if (isFeature(selection)) {
82
+ onFeatureSelect(selection.toJSON());
83
+ }
84
+ }));
85
+ }
86
+ return () => {
87
+ for (const dispose of disposers) {
88
+ dispose();
89
+ }
90
+ };
91
+ }
@@ -0,0 +1,32 @@
1
+ import type { PluginConstructor } from '@jbrowse/core/Plugin';
2
+ import type { PluginRecord } from '@jbrowse/core/PluginLoader';
3
+ /**
4
+ * A plugin an embedded product's caller can register: either the class itself,
5
+ * or the `{ plugin, definition }` record `loadPlugins` returns.
6
+ *
7
+ * Prefer passing the record straight through. The `definition` is what lets the
8
+ * RPC worker load the same plugin on its side — hand it only the class and the
9
+ * plugin exists on the main thread but not in the worker, so anything it
10
+ * contributes that runs there (an adapter, most commonly) fails to resolve.
11
+ */
12
+ export type PluginInput = PluginConstructor | PluginRecord;
13
+ /**
14
+ * Turn caller-supplied plugins into `PluginManager` load records, keeping each
15
+ * runtime plugin's `definition` when it has one.
16
+ *
17
+ * That definition is the whole point: `PluginManager.addPlugin` only records
18
+ * `runtimePluginDefinitions` for records that carry one, and `RpcManager` ships
19
+ * that list to the RPC worker as its boot config so the worker can fetch the
20
+ * same plugin. Dropping it leaves the plugin main-thread-only, and the failure
21
+ * surfaces far away — as an unknown adapter/track type inside the worker.
22
+ *
23
+ * Shared by every embedded product rather than restated, because getting it
24
+ * wrong is invisible until someone combines a runtime plugin with a worker.
25
+ */
26
+ export declare function toPluginLoadRecord(p: PluginInput): {
27
+ plugin: import("@jbrowse/core/Plugin").default;
28
+ definition?: undefined;
29
+ } | {
30
+ plugin: import("@jbrowse/core/Plugin").default;
31
+ definition: import("@jbrowse/core/pluginDefinitions").PluginDefinition;
32
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Turn caller-supplied plugins into `PluginManager` load records, keeping each
3
+ * runtime plugin's `definition` when it has one.
4
+ *
5
+ * That definition is the whole point: `PluginManager.addPlugin` only records
6
+ * `runtimePluginDefinitions` for records that carry one, and `RpcManager` ships
7
+ * that list to the RPC worker as its boot config so the worker can fetch the
8
+ * same plugin. Dropping it leaves the plugin main-thread-only, and the failure
9
+ * surfaces far away — as an unknown adapter/track type inside the worker.
10
+ *
11
+ * Shared by every embedded product rather than restated, because getting it
12
+ * wrong is invisible until someone combines a runtime plugin with a worker.
13
+ */
14
+ export function toPluginLoadRecord(p) {
15
+ // a plugin class is a function; a loadPlugins record is a plain object
16
+ return typeof p === 'function'
17
+ ? { plugin: new p() }
18
+ : { plugin: new p.plugin(), definition: p.definition };
19
+ }
@@ -0,0 +1,109 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ import type { PluggableElementTypeGroup } from '@jbrowse/core/PluginManager';
3
+ import type { IAnyType } from '@jbrowse/mobx-state-tree';
4
+ /**
5
+ * A session snapshot that arrives from somewhere else — a share link, a desktop
6
+ * "export to web" URL, a session.json — can name a pluggable type this build has
7
+ * no plugin for. Every such collection is `types.map`/`types.array` over a bare
8
+ * `pluggableMstType` union with no dispatcher, so one entry the union cannot
9
+ * match throws `No type is applicable for the union` out of the `cast` in
10
+ * `setSession` — outside the try that `filterSessionInPlace` sits in. The whole
11
+ * session is lost, and jbrowse-web's fallback then advises the recipient to ask
12
+ * for a link made with the Share button, which is often exactly what they were
13
+ * sent.
14
+ *
15
+ * Two routes reach it and neither is exotic. A desktop export carries session
16
+ * state built from desktop's own core plugins — `blat` registers
17
+ * `UcscResultsWidget`, and every BLAT or in-silico PCR search leaves one in the
18
+ * drawer — which jbrowse-web does not have (see
19
+ * `scripts/check-core-plugin-sets.ts` for the exact relation between the
20
+ * products' lists). And a runtime plugin that fails to load is already
21
+ * tolerated: `notifyPluginLoadFailures` says the session opens without it, which
22
+ * was untrue for any session actually using its types.
23
+ *
24
+ * So the node comes out of the tree and the session opens. `dropped` is what the
25
+ * caller tells the user, which is the only part of this a person can act on.
26
+ *
27
+ * **The node is held, not discarded.** Taking it out of the tree is only half
28
+ * the job: jbrowse-web autosaves `getSnapshot(session)` on a 400ms debounce
29
+ * (`rootModel/persistence.ts`), so a recipient who merely opens a shared link
30
+ * without the plugin writes the reduced session back over the stored row — and
31
+ * reshares it that way. The removed nodes ride along under
32
+ * `heldForMissingPlugins` instead, and a build that has the plugin puts them
33
+ * back. Installing the plugin and reloading is then enough to get the session
34
+ * whole again, which is what makes a plugin removable at all.
35
+ *
36
+ * **The session schema is read off the session MST type, not written down here.**
37
+ * One walk descends `(type, snapshot)` in lockstep: at any `types.array` or
38
+ * `types.map` whose element type is one of the `pluggableMstType` unions it
39
+ * admits, holds or restores each entry, and everywhere else it recurses
40
+ * structurally. That is what reaches a synteny view's `levels[].tracks` and the
41
+ * session's `connectionInstances`, which a hand-written list of `views` /
42
+ * `tracks` / `displays` / `widgets` did not, and what will reach the next
43
+ * container someone adds without anyone editing this file.
44
+ *
45
+ * **A node is held only when all three tests fail**: its `type` names no
46
+ * registered element, no element declares it as an `alias`, and
47
+ * the real MST union refuses the snapshot. The last two are what keep a renamed
48
+ * type alive — an element can declare `aliases`, and a model's own
49
+ * `preProcessSnapshot` can rewrite the type literal
50
+ * (`LinearMultiSampleVariantDisplay` does both for the old
51
+ * `MultiLinearVariantDisplay`), neither of which the registry's names show. The
52
+ * first is what keeps this from quietly swallowing a *malformed* snapshot of a
53
+ * type this build does have: that still throws, exactly as before, because
54
+ * holding it would hide a real bug behind a message about plugins.
55
+ */
56
+ type Group = PluggableElementTypeGroup;
57
+ export interface UnbuildableNode {
58
+ group: Group;
59
+ type: string;
60
+ cascade?: true;
61
+ }
62
+ /**
63
+ * One node taken out of the tree, with the anchor that puts it back.
64
+ *
65
+ * The anchor is a parent **id** plus a position, never a path: paths are
66
+ * index-based and every edit the recipient makes shifts them, while a view id
67
+ * and a track id are stable for as long as the container exists. A held node
68
+ * whose parent the recipient has since deleted is not restorable and is not
69
+ * meant to be — the container going is the user saying so.
70
+ */
71
+ export interface HeldNode {
72
+ group: Group;
73
+ /** map key, for a node that came out of a `types.map` */
74
+ key?: string;
75
+ /**
76
+ * id of the nearest enclosing node that has one — the view a track came out
77
+ * of, the track a display came out of, the synteny level a level's track came
78
+ * out of. Absent for a container on the session itself.
79
+ */
80
+ parent?: string;
81
+ /**
82
+ * position in the list it came out of, so a restore lands where it was.
83
+ * Absent for a map entry, which `key` places instead.
84
+ */
85
+ index?: number;
86
+ snapshot: Record<string, unknown>;
87
+ }
88
+ /**
89
+ * Takes the session-snapshot nodes whose pluggable type this plugin manager
90
+ * cannot build out of the tree and holds them under `heldForMissingPlugins`,
91
+ * and puts back the ones already held that it now can build — in one walk of
92
+ * the session type, so the two can never disagree about where a node lives.
93
+ * Returns the snapshot unchanged (by identity) when there is nothing to do
94
+ * either way, which is every session this build produced itself.
95
+ */
96
+ export declare function pruneUnbuildableNodes(snapshot: Record<string, unknown>, pluginManager: PluginManager, sessionModelType: IAnyType): {
97
+ snapshot: Record<string, unknown>;
98
+ dropped: UnbuildableNode[];
99
+ };
100
+ /**
101
+ * The user-facing summary of one prune, or undefined when nothing was dropped.
102
+ * Names the missing types rather than counting nodes: the plugin a person has to
103
+ * install is the actionable half, and one missing display type can take several
104
+ * nodes with it. Says what a person can do about it, because the nodes are not
105
+ * gone — they ride along under `heldForMissingPlugins` and come back the moment
106
+ * a build that has the plugin opens the session.
107
+ */
108
+ export declare function describeUnbuildableNodes(dropped: UnbuildableNode[]): string | undefined;
109
+ export {};