@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.
Files changed (157) hide show
  1. package/esm/RootModel/BaseRootModel.d.ts +514 -323
  2. package/esm/RootModel/BaseRootModel.js +141 -14
  3. package/esm/RootModel/HierarchicalConfig.d.ts +30 -20
  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 +37 -0
  8. package/esm/RootModel/PreferencesConfig.js +75 -0
  9. package/esm/RootModel/createConfigModel.d.ts +257 -0
  10. package/esm/RootModel/createConfigModel.js +77 -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 +141 -0
  15. package/esm/Session/BaseSession.d.ts +285 -11
  16. package/esm/Session/BaseSession.js +383 -15
  17. package/esm/Session/Connections.d.ts +115 -8
  18. package/esm/Session/Connections.js +196 -74
  19. package/esm/Session/DrawerWidgets.d.ts +145 -13
  20. package/esm/Session/DrawerWidgets.js +219 -49
  21. package/esm/Session/MultipleViews.d.ts +1067 -1177
  22. package/esm/Session/MultipleViews.js +395 -64
  23. package/esm/Session/Preferences.d.ts +1389 -0
  24. package/esm/Session/Preferences.js +58 -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 +1101 -1180
  28. package/esm/Session/SessionTracks.js +584 -34
  29. package/esm/Session/Themes.d.ts +136 -2
  30. package/esm/Session/Themes.js +267 -21
  31. package/esm/Session/TrackMenu.d.ts +88 -0
  32. package/esm/Session/TrackMenu.js +175 -0
  33. package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
  34. package/esm/Session/TrackMenuSessionMixin.js +42 -0
  35. package/esm/Session/Tracks.d.ts +945 -1159
  36. package/esm/Session/Tracks.js +229 -30
  37. package/esm/Session/assertSessionModel.d.ts +21 -0
  38. package/esm/Session/assertSessionModel.js +1 -0
  39. package/esm/Session/copyTrackSnapshot.d.ts +21 -0
  40. package/esm/Session/copyTrackSnapshot.js +20 -0
  41. package/esm/Session/developerMode.d.ts +7 -0
  42. package/esm/Session/developerMode.js +43 -0
  43. package/esm/Session/finalizeSession.d.ts +15 -0
  44. package/esm/Session/finalizeSession.js +28 -0
  45. package/esm/Session/hydratedForms.d.ts +16 -0
  46. package/esm/Session/hydratedForms.js +13 -0
  47. package/esm/Session/index.d.ts +27 -10
  48. package/esm/Session/index.js +16 -10
  49. package/esm/Session/pluginExtraTrackItems.d.ts +11 -0
  50. package/esm/Session/pluginExtraTrackItems.js +13 -0
  51. package/esm/Session/readdedTrackConf.d.ts +8 -0
  52. package/esm/Session/readdedTrackConf.js +24 -0
  53. package/esm/Session/shareableSnapshot.d.ts +23 -0
  54. package/esm/Session/shareableSnapshot.js +66 -0
  55. package/esm/Session/temporaryAssemblyTracks.d.ts +24 -0
  56. package/esm/Session/temporaryAssemblyTracks.js +37 -0
  57. package/esm/assertExtends.d.ts +55 -0
  58. package/esm/assertExtends.js +1 -0
  59. package/esm/controllerTracks.d.ts +76 -0
  60. package/esm/controllerTracks.js +125 -0
  61. package/esm/destroyViewState.d.ts +21 -0
  62. package/esm/destroyViewState.js +29 -0
  63. package/esm/embeddedController.d.ts +57 -0
  64. package/esm/embeddedController.js +100 -0
  65. package/esm/index.d.ts +32 -4
  66. package/esm/index.js +31 -4
  67. package/esm/loadPlugins.d.ts +38 -0
  68. package/esm/loadPlugins.js +45 -0
  69. package/esm/localFiles.d.ts +74 -0
  70. package/esm/localFiles.js +156 -0
  71. package/esm/observeSession.d.ts +55 -0
  72. package/esm/observeSession.js +90 -0
  73. package/esm/parallelChunkLoading.d.ts +30 -0
  74. package/esm/parallelChunkLoading.js +71 -0
  75. package/esm/pluginInput.d.ts +32 -0
  76. package/esm/pluginInput.js +19 -0
  77. package/esm/pruneUnbuildableNodes.d.ts +109 -0
  78. package/esm/pruneUnbuildableNodes.js +562 -0
  79. package/esm/resolveAssemblies.d.ts +68 -0
  80. package/esm/resolveAssemblies.js +77 -0
  81. package/esm/rpcWorker.d.ts +36 -4
  82. package/esm/rpcWorker.js +126 -20
  83. package/esm/sessionMigrations/index.d.ts +15 -0
  84. package/esm/sessionMigrations/index.js +178 -0
  85. package/esm/sessionUrl.d.ts +40 -0
  86. package/esm/sessionUrl.js +66 -0
  87. package/esm/sessionUtils.d.ts +74 -0
  88. package/esm/sessionUtils.js +599 -0
  89. package/esm/siblingCast.d.ts +14 -0
  90. package/esm/siblingCast.js +16 -0
  91. package/esm/snapshotUtils.d.ts +8 -0
  92. package/esm/snapshotUtils.js +12 -0
  93. package/esm/ui/AboutDialog.d.ts +8 -3
  94. package/esm/ui/AboutDialog.js +14 -5
  95. package/esm/ui/AboutDialogContents.d.ts +2 -6
  96. package/esm/ui/AboutDialogContents.js +13 -17
  97. package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
  98. package/esm/ui/AssemblyInfoPanel.js +63 -0
  99. package/esm/ui/DescriptionPanel.d.ts +3 -0
  100. package/esm/ui/DescriptionPanel.js +25 -0
  101. package/esm/ui/Drawer.d.ts +21 -0
  102. package/esm/ui/Drawer.js +60 -0
  103. package/esm/ui/DrawerControls.d.ts +5 -0
  104. package/esm/ui/DrawerControls.js +28 -0
  105. package/esm/ui/DrawerHeader.d.ts +6 -0
  106. package/esm/ui/DrawerHeader.js +36 -0
  107. package/esm/ui/DrawerWidget.d.ts +5 -0
  108. package/esm/ui/DrawerWidget.js +12 -0
  109. package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
  110. package/esm/ui/DrawerWidgetSelector.js +40 -0
  111. package/esm/ui/FileInfoPanel.d.ts +3 -6
  112. package/esm/ui/FileInfoPanel.js +36 -26
  113. package/esm/ui/GeneralPreferencesTab.d.ts +17 -0
  114. package/esm/ui/GeneralPreferencesTab.js +34 -0
  115. package/esm/ui/HeaderButtons.d.ts +2 -1
  116. package/esm/ui/HeaderButtons.js +15 -17
  117. package/esm/ui/ModalWidget.d.ts +13 -0
  118. package/esm/ui/ModalWidget.js +48 -0
  119. package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
  120. package/esm/ui/ModalWidgetAppBar.js +9 -0
  121. package/esm/ui/PreferenceCheckbox.d.ts +6 -0
  122. package/esm/ui/PreferenceCheckbox.js +16 -0
  123. package/esm/ui/PreferencesDialog.d.ts +34 -0
  124. package/esm/ui/PreferencesDialog.js +55 -0
  125. package/esm/ui/PreferencesResetDialog.d.ts +8 -0
  126. package/esm/ui/PreferencesResetDialog.js +19 -0
  127. package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
  128. package/esm/ui/RefNameAliasesDialog.js +65 -0
  129. package/esm/ui/RefNameInfoDialog.d.ts +3 -6
  130. package/esm/ui/RefNameInfoDialog.js +45 -63
  131. package/esm/ui/ViewPreferencesTab.d.ts +12 -0
  132. package/esm/ui/ViewPreferencesTab.js +15 -0
  133. package/esm/ui/WidgetBody.d.ts +18 -0
  134. package/esm/ui/WidgetBody.js +22 -0
  135. package/esm/ui/WidgetHeading.d.ts +7 -0
  136. package/esm/ui/WidgetHeading.js +7 -0
  137. package/esm/ui/aboutTestUtils.d.ts +140 -0
  138. package/esm/ui/aboutTestUtils.js +25 -0
  139. package/esm/ui/descriptionHtml.d.ts +25 -0
  140. package/esm/ui/descriptionHtml.js +64 -0
  141. package/esm/ui/drawerLayout.d.ts +18 -0
  142. package/esm/ui/drawerLayout.js +22 -0
  143. package/esm/ui/index.d.ts +15 -1
  144. package/esm/ui/index.js +13 -1
  145. package/esm/ui/preferencesReset.d.ts +18 -0
  146. package/esm/ui/preferencesReset.js +69 -0
  147. package/esm/ui/util.d.ts +22 -7
  148. package/esm/ui/util.js +19 -41
  149. package/esm/useEngineLifecycle.d.ts +19 -0
  150. package/esm/useEngineLifecycle.js +44 -0
  151. package/package.json +15 -18
  152. package/esm/RootModel/FormatAbout.d.ts +0 -12
  153. package/esm/RootModel/FormatAbout.js +0 -15
  154. package/esm/RootModel/FormatDetails.d.ts +0 -24
  155. package/esm/RootModel/FormatDetails.js +0 -27
  156. package/esm/Session/DialogQueue.d.ts +0 -17
  157. package/esm/Session/DialogQueue.js +0 -31
package/esm/index.js CHANGED
@@ -1,4 +1,31 @@
1
- export * from "./RootModel/index.js";
2
- export * from "./Session/index.js";
3
- export * from "./ui/index.js";
4
- export * from "./rpcWorker.js";
1
+ export { asRoot, asSession } from './siblingCast.js';
2
+ export { scheduleDetachedDestroy } from '@jbrowse/core/util';
3
+ export { BaseRootModelFactory, FormatAboutConfigSchemaFactory, FormatDetailsConfigSchemaFactory, HierarchicalConfigSchemaFactory, InternetAccountsRootModelMixin, PreferencesConfigSchemaFactory, createConfigModel, exportSessionMenuItem, importSessionMenuItem, isRootModel, newSessionMenuItem, openConnectionMenuItem, openTrackMenuItem, pluginStoreMenuItem, preferencesMenuItem, redoMenuItem, undoMenuItem, workspacesMenuItem, } from './RootModel/index.js';
4
+ export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, hydratedForms, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, storedThemeArgs, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.js';
5
+ export { AboutDialog, AppReadyMarker, Drawer, DrawerControls, DrawerHeader, DrawerWidget, DrawerWidgetSelector, ModalWidget, ModalWidgetAppBar, PreferencesDialog, WidgetBody, WidgetHeading, drawerGridTemplateColumns, } from './ui/index.js';
6
+ export { initializeWorker } from './rpcWorker.js';
7
+ export { toPluginLoadRecord } from './pluginInput.js';
8
+ export { loadRuntimePlugins } from './loadPlugins.js';
9
+ export { decodeSessionFromUrl, encodeSessionToUrl } from './sessionUrl.js';
10
+ // in-memory files as tracks, for hosts whose data lives in a process rather
11
+ // than at a URL (a notebook kernel, an R session) — read by byte range, so an
12
+ // indexed file stays indexed
13
+ export { normalizeAdapterSnapshots, registerLocalFiles, resolveLocalFileUris, } from './localFiles.js';
14
+ export { searchIndexKey, withHostOverrides } from './controllerTracks.js';
15
+ // the lifecycle behind each single-view product's imperative controller
16
+ export { createEmbeddedController } from './embeddedController.js';
17
+ // hub names, sequence URIs and hub configs -> assembly configs, so every
18
+ // product takes the same assembly vocabulary
19
+ export { resolveAssemblies, resolveAssembly } from './resolveAssemblies.js';
20
+ // engine teardown, for a host that builds and discards engines — React unmount
21
+ // alone leaves the RPC workers and autoruns running
22
+ export { destroyViewState } from './destroyViewState.js';
23
+ // the two halves of "React owns this engine", both of which have a StrictMode
24
+ // trap in them that the obvious spelling walks straight into
25
+ export { useAsyncEngineLifecycle, useCreateOnce, useCreateOnceAsync, useDestroyOnUnmount, } from './useEngineLifecycle.js';
26
+ // the JS -> host direction of an embedded controller: where each view is
27
+ // looking, what got selected, and the layout as plain JSON
28
+ export { getSessionSnapshot, observeSession } from './observeSession.js';
29
+ export { migrateSessionSnapshot, migratedDisplayInstanceKeys, } from './sessionMigrations/index.js';
30
+ export { DEFAULT_WEB_BASE_URL, analyzeWebPortability, buildWebExportUrl, filterSessionInPlace, planWebExport, } from './sessionUtils.js';
31
+ export { describeUnbuildableNodes, pruneUnbuildableNodes, } from './pruneUnbuildableNodes.js';
@@ -0,0 +1,38 @@
1
+ import type { LoadedPlugin, ReExportRegistryLoader } from '@jbrowse/core/PluginLoader';
2
+ import type { PluginDefinition } from '@jbrowse/core/pluginDefinitions';
3
+ export interface LoadPluginsArgs {
4
+ fetchESM?: (url: string) => Promise<LoadedPlugin>;
5
+ /**
6
+ * Resolve relative plugin urls against this instead of the page. A config
7
+ * fetched from somewhere else names its plugins relative to itself, so pass
8
+ * that config's url — otherwise a `"url": "umd_plugin.js"` entry is looked
9
+ * for next to your own app and 404s.
10
+ */
11
+ baseUri?: string;
12
+ /** @deprecated the pre-4.4 spelling of `baseUri` */
13
+ baseUrl?: string;
14
+ }
15
+ /**
16
+ * The body of every embedded product's `loadPlugins`. Each product keeps its own
17
+ * one-line wrapper, because the doc comment on it is the published API surface
18
+ * and names that product's own entry points — but the behavior is here, so the
19
+ * two things that differed between the copies can't drift again: react-app's
20
+ * accepted no `baseUrl`, and only two of the three defaulted the base url at
21
+ * all.
22
+ *
23
+ * `dropVendored` is the one difference that is real, so it is a required
24
+ * argument rather than a default. A product that bundles the vendored plugins
25
+ * (see `vendoredPluginNames`) must drop a config's entry for them or it installs
26
+ * a second copy beside its own; a product that does not bundle them — the
27
+ * circular-genome-view build has neither MafViewer nor GWAS in its corePlugins —
28
+ * must still fetch one a config names, or the config silently loses the plugin.
29
+ *
30
+ * `reExports` is the product's `reExports.generated.ts`, which serves every
31
+ * `@jbrowse` package the product bundles. Required for the same reason: a
32
+ * plugin handed core's map alone cannot read the rest.
33
+ */
34
+ export declare function loadRuntimePlugins(pluginDefinitions: PluginDefinition[], { dropVendored, jbrowseVersion, reExports, ...args }: LoadPluginsArgs & {
35
+ dropVendored: boolean;
36
+ jbrowseVersion: string;
37
+ reExports: ReExportRegistryLoader;
38
+ }): Promise<never[] | import("@jbrowse/core/PluginLoader").PluginRecord[]>;
@@ -0,0 +1,45 @@
1
+ import PluginLoader from '@jbrowse/core/PluginLoader';
2
+ import { dropVendoredPlugins } from '@jbrowse/core/pluginDefinitions';
3
+ import { resolveStorePluginRefs } from '@jbrowse/core/util';
4
+ /**
5
+ * The body of every embedded product's `loadPlugins`. Each product keeps its own
6
+ * one-line wrapper, because the doc comment on it is the published API surface
7
+ * and names that product's own entry points — but the behavior is here, so the
8
+ * two things that differed between the copies can't drift again: react-app's
9
+ * accepted no `baseUrl`, and only two of the three defaulted the base url at
10
+ * all.
11
+ *
12
+ * `dropVendored` is the one difference that is real, so it is a required
13
+ * argument rather than a default. A product that bundles the vendored plugins
14
+ * (see `vendoredPluginNames`) must drop a config's entry for them or it installs
15
+ * a second copy beside its own; a product that does not bundle them — the
16
+ * circular-genome-view build has neither MafViewer nor GWAS in its corePlugins —
17
+ * must still fetch one a config names, or the config silently loses the plugin.
18
+ *
19
+ * `reExports` is the product's `reExports.generated.ts`, which serves every
20
+ * `@jbrowse` package the product bundles. Required for the same reason: a
21
+ * plugin handed core's map alone cannot read the rest.
22
+ */
23
+ export async function loadRuntimePlugins(pluginDefinitions, { dropVendored, jbrowseVersion, reExports, ...args }) {
24
+ // Before dropVendoredPlugins, which matches on the UMD name a ref does not
25
+ // carry until the store supplies it. `jbrowseVersion` is required rather than
26
+ // defaulted so a product that forgets it fails to compile instead of quietly
27
+ // resolving every range against the wrong host.
28
+ const { definitions, failures } = await resolveStorePluginRefs(pluginDefinitions, jbrowseVersion);
29
+ // This path ends in `load`, which is all-or-nothing (see below) — so a ref
30
+ // that resolved to nothing is a failure now rather than a plugin the embedder
31
+ // silently never receives.
32
+ if (failures[0]) {
33
+ throw failures[0].error;
34
+ }
35
+ const toLoad = dropVendored ? dropVendoredPlugins(definitions) : definitions;
36
+ // the default matters: `new URL('umd_plugin.js', undefined)` throws, so a
37
+ // relative plugin url used to fail outright instead of resolving against the
38
+ // page the way every other relative url does
39
+ const base = args.baseUri ?? args.baseUrl;
40
+ return toLoad.length
41
+ ? new PluginLoader(toLoad, args)
42
+ .installGlobalReExports(window, reExports)
43
+ .load(base ?? window.location.href)
44
+ : [];
45
+ }
@@ -0,0 +1,74 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ import type { BlobLocation } from '@jbrowse/core/util';
3
+ /**
4
+ * Bytes for one browser-local file, keyed by the name a track config refers to
5
+ * it by. `Blob`/`File` pass through; anything else (a `Uint8Array` from a
6
+ * notebook kernel's binary channel, an `ArrayBuffer` from a fetch) is wrapped.
7
+ */
8
+ export type LocalFileInput = Record<string, Blob | BufferSource>;
9
+ /**
10
+ * Register in-memory bytes as browser-local files JBrowse can read, returning
11
+ * the `name -> BlobLocation` map {@link resolveLocalFileUris} substitutes with.
12
+ *
13
+ * This is the escape from "the data has to be on a web server first". A
14
+ * `BlobLocation` opens as a `BlobFile`, which serves byte ranges out of
15
+ * `Blob.slice()` — so a bgzipped+tabixed file, a BAM with its `.bai`, or a
16
+ * bigWig is *seeked into* exactly as if it were remote, and only the bytes the
17
+ * current view needs are ever touched. That is the whole point of doing this
18
+ * rather than inlining features into a config: a 200k-feature table is ~20MB of
19
+ * JSON to serialize, ship, and hold in memory, while the same data as a tabix
20
+ * file is a few MB the view reads a few KB of at a time.
21
+ *
22
+ * For hosts that hold their data in a process rather than at a URL — a notebook
23
+ * kernel, an R session, a desktop app — this is the way in, and it needs no web
24
+ * server, no CORS, and no temporary public bucket.
25
+ */
26
+ export declare function registerLocalFiles(files: LocalFileInput): {
27
+ [k: string]: BlobLocation;
28
+ };
29
+ /**
30
+ * Fold newly-offered files into a controller's registered map, registering only
31
+ * the names it has not seen. Registering is what mints the blobIds, so a host
32
+ * re-stating its whole file set on every update — which is what a declarative
33
+ * host does — pays for each file once rather than once per update.
34
+ *
35
+ * Only ever adds: a name already registered keeps its existing blob, because a
36
+ * live track config points at that blobId and swapping it underneath would
37
+ * leave the track reading bytes nothing else references.
38
+ */
39
+ export declare function mergeLocalFiles(current: Record<string, BlobLocation>, files: LocalFileInput): {
40
+ [x: string]: BlobLocation;
41
+ };
42
+ /**
43
+ * Expand every shorthand adapter snapshot (`{ type, uri }`) in a config to the
44
+ * canonical location keys its config schema declares, using that adapter type's
45
+ * own `normalizeSnapshot` — the hook that exists so "downstream code can read
46
+ * location keys without knowing each shorthand".
47
+ *
48
+ * {@link resolveLocalFileUris} has to run on the expanded form, and this is the
49
+ * difference between `localFiles` working and silently doing nothing: the
50
+ * shorthand puts `uri` on the *adapter*, so substituting there would replace
51
+ * `{ type: 'BamAdapter', uri: 'x.bam' }` with a bare BlobLocation and take the
52
+ * adapter's type with it. Expanding first moves the name onto `bamLocation`,
53
+ * where it is a location node and substitution is what it looks like. The
54
+ * expansion also derives the conventional index sibling (`.bai`/`.tbi`/`.crai`)
55
+ * from the same string, so registering `x.bam` and `x.bam.bai` is enough and no
56
+ * caller has to know which adapter wanted which file.
57
+ *
58
+ * Idempotent, and a no-op for an already-canonical config or an adapter type
59
+ * that declares no shorthand.
60
+ */
61
+ export declare function normalizeAdapterSnapshots<T>(config: T, pluginManager: PluginManager): T;
62
+ /**
63
+ * Rewrite every `{ uri: <a registered name> }` in a track config to that file's
64
+ * `BlobLocation`, in place of nothing else — unregistered URIs are left alone,
65
+ * so a config can mix local and remote files freely.
66
+ *
67
+ * Runs on a config whose locations are already canonical, which is what
68
+ * `guessTrackConf` (for a loose track spec) and {@link normalizeAdapterSnapshots}
69
+ * (for a written-out one) each produce. So a host registers `peaks.bed.gz` and
70
+ * `peaks.bed.gz.tbi` under their plain names, the config names both under
71
+ * `bedGzLocation` and `index.location`, and this swaps both for blobs — index
72
+ * and all, with no host-side knowledge of which adapter wanted which sibling.
73
+ */
74
+ export declare function resolveLocalFileUris<T>(config: T, locations: Record<string, BlobLocation>): T;
@@ -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,55 @@
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
+ export 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: a
47
+ * workspace arrangement turned on by the sender's preference is not in the
48
+ * snapshot, so a raw one replays as a classic stack for whoever opens it.
49
+ */
50
+ export declare function getSessionSnapshot(session: ObservedSession): SessionSnapshot;
51
+ /**
52
+ * Wire the read-backs a host asked for, returning one disposer for all of them.
53
+ */
54
+ export declare function observeSession(root: ObservedRoot, { onLocationChange, onFeatureSelect, onSessionChange }: SessionObservers): () => void;
55
+ export {};
@@ -0,0 +1,90 @@
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: a
11
+ * workspace arrangement turned on by the sender's preference is not in the
12
+ * snapshot, so a raw one replays as a classic stack for whoever opens it.
13
+ */
14
+ export function getSessionSnapshot(session) {
15
+ // `name` is restated rather than the whole thing cast: the bake returns an
16
+ // open record, but the model guarantees the session has one
17
+ return {
18
+ ...getShareableSessionSnapshot(session),
19
+ name: session.name,
20
+ };
21
+ }
22
+ // A linear view reports its visible region as one coarseVisibleLocStrings
23
+ // string; a comparative view (synteny/dotplot) has no single string, so report
24
+ // one entry per panel instead.
25
+ function viewLocation(view) {
26
+ return typeof view.coarseVisibleLocStrings === 'string'
27
+ ? view.coarseVisibleLocStrings || undefined
28
+ : view.views
29
+ ?.map(v => v.coarseVisibleLocStrings)
30
+ .filter(loc => loc !== undefined);
31
+ }
32
+ // Every trackId a view has open. A synteny view keeps its tracks per level
33
+ // rather than in one flat list, so reading `tracks` alone reports none of them —
34
+ // and the read-back then never fired when a synteny track was opened.
35
+ function viewTracks(view) {
36
+ return (view.tracks ?? view.levels?.flatMap(l => l.tracks) ?? [])
37
+ .map(t => t.configuration.trackId)
38
+ .join(',');
39
+ }
40
+ // The coarse signal the read-backs ride on: which views exist, what each has
41
+ // open, and where each is looking. Deliberately NOT the whole snapshot —
42
+ // depending on that would pull in offsetPx, and a snapshot is kilobytes, so a
43
+ // drag would push one per pointer event across the host's wire.
44
+ // coarseVisibleLocStrings is JBrowse's own debounced location, so this settles
45
+ // after a pan rather than firing during it.
46
+ //
47
+ // A plain function, not an action: actions run untracked, and computing this is
48
+ // precisely what subscribes the autorun below.
49
+ function layoutSignal(views) {
50
+ return views
51
+ .map(v => `${v.id}:${JSON.stringify(viewLocation(v))}:${viewTracks(v)}`)
52
+ .join('|');
53
+ }
54
+ /**
55
+ * Wire the read-backs a host asked for, returning one disposer for all of them.
56
+ */
57
+ export function observeSession(root, { onLocationChange, onFeatureSelect, onSessionChange }) {
58
+ const disposers = [];
59
+ // `session` is read inside each autorun and never captured: setSession
60
+ // replaces the whole node (`self.session = cast(...)` in BaseRootModel), so a
61
+ // captured one is a dead MST node the moment a session is restored, and the
62
+ // read-back would go silent for the rest of the product's life.
63
+ if (onLocationChange ?? onSessionChange) {
64
+ disposers.push(autorun(() => {
65
+ const { session } = root;
66
+ if (!session) {
67
+ return;
68
+ }
69
+ // the only dependency this autorun should have
70
+ layoutSignal(session.views);
71
+ onLocationChange?.(session.views.map(viewLocation));
72
+ // untracked, so reading the snapshot adds no dependencies of its own
73
+ // eslint-disable-next-line no-restricted-syntax -- effect input: the callback consumes the snapshot, the views are the trigger
74
+ onSessionChange?.(untracked(() => getSessionSnapshot(session)));
75
+ }));
76
+ }
77
+ if (onFeatureSelect) {
78
+ disposers.push(autorun(() => {
79
+ const selection = root.session?.selection;
80
+ if (isFeature(selection)) {
81
+ onFeatureSelect(selection.toJSON());
82
+ }
83
+ }));
84
+ }
85
+ return () => {
86
+ for (const dispose of disposers) {
87
+ dispose();
88
+ }
89
+ };
90
+ }
@@ -0,0 +1,30 @@
1
+ type ChunkId = string | number;
2
+ type ChunkHandler = (chunkId: ChunkId, promises: Promise<unknown>[]) => void;
3
+ export interface WebpackChunkRuntime {
4
+ f?: Record<string, ChunkHandler | undefined>;
5
+ }
6
+ export interface WorkerScope {
7
+ importScripts: (url: string) => void;
8
+ location: {
9
+ href: string;
10
+ };
11
+ fetch: (url: string) => Promise<{
12
+ arrayBuffer: () => Promise<unknown>;
13
+ }>;
14
+ }
15
+ /**
16
+ * Download the chunks of a dynamic import together in a classic web worker.
17
+ *
18
+ * Webpack's worker chunk handler (`f.i`) runs `importScripts` for one chunk at
19
+ * a time, and `importScripts` blocks until its file has arrived, so an import
20
+ * spanning 26 chunks waits on 26 round trips in a row. This handler asks the
21
+ * original which url it would load (none, if the chunk is installed), fetches
22
+ * it, and runs `importScripts` once the fetch lands, by then a cache read. A
23
+ * chunk file only registers module factories, so install order is free.
24
+ *
25
+ * Only same-origin http(s) chunks are prefetched: another origin would need
26
+ * CORS for `fetch`, and a `file:` worker (Desktop) has no round trip to save.
27
+ */
28
+ export declare function parallelizeChunkLoading(runtime: WebpackChunkRuntime | undefined, scope: WorkerScope): void;
29
+ export declare function parallelizeWorkerChunkLoading(): void;
30
+ export {};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Download the chunks of a dynamic import together in a classic web worker.
3
+ *
4
+ * Webpack's worker chunk handler (`f.i`) runs `importScripts` for one chunk at
5
+ * a time, and `importScripts` blocks until its file has arrived, so an import
6
+ * spanning 26 chunks waits on 26 round trips in a row. This handler asks the
7
+ * original which url it would load (none, if the chunk is installed), fetches
8
+ * it, and runs `importScripts` once the fetch lands, by then a cache read. A
9
+ * chunk file only registers module factories, so install order is free.
10
+ *
11
+ * Only same-origin http(s) chunks are prefetched: another origin would need
12
+ * CORS for `fetch`, and a `file:` worker (Desktop) has no round trip to save.
13
+ */
14
+ export function parallelizeChunkLoading(runtime, scope) {
15
+ const handlers = runtime?.f;
16
+ const original = handlers?.i;
17
+ if (!handlers || !original) {
18
+ return;
19
+ }
20
+ const { origin, protocol } = new URL(scope.location.href);
21
+ if (protocol !== 'http:' && protocol !== 'https:') {
22
+ return;
23
+ }
24
+ const importScripts = scope.importScripts.bind(scope);
25
+ const loading = new Map();
26
+ const urlOriginalWouldLoad = (chunkId, promises) => {
27
+ let url;
28
+ scope.importScripts = (u) => {
29
+ url = u;
30
+ };
31
+ try {
32
+ original(chunkId, promises);
33
+ }
34
+ finally {
35
+ scope.importScripts = importScripts;
36
+ }
37
+ return url;
38
+ };
39
+ handlers.i = (chunkId, promises) => {
40
+ const url = urlOriginalWouldLoad(chunkId, promises);
41
+ if (url === undefined) {
42
+ return;
43
+ }
44
+ if (new URL(url, scope.location.href).origin !== origin) {
45
+ importScripts(url);
46
+ return;
47
+ }
48
+ let pending = loading.get(url);
49
+ if (!pending) {
50
+ pending = scope
51
+ .fetch(url)
52
+ .then(r => r.arrayBuffer())
53
+ .then(() => { }, () => { })
54
+ .then(() => {
55
+ importScripts(url);
56
+ });
57
+ pending.catch(() => {
58
+ loading.delete(url);
59
+ });
60
+ loading.set(url, pending);
61
+ }
62
+ promises.push(pending);
63
+ };
64
+ }
65
+ export function parallelizeWorkerChunkLoading() {
66
+ parallelizeChunkLoading(
67
+ // eslint-disable-next-line unicorn/no-typeof-undefined -- undeclared outside webpack, so a direct read throws
68
+ typeof __webpack_require__ === 'undefined'
69
+ ? undefined
70
+ : __webpack_require__, globalThis);
71
+ }
@@ -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
+ }