@jbrowse/core 5.0.0-beta.1 → 5.0.0-beta.2

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 (115) hide show
  1. package/README.md +42 -3
  2. package/esm/BaseFeatureWidget/SequenceFeatureDetails/test_data/NCDN.d.ts +2 -2
  3. package/esm/PluginLoader.d.ts +1 -0
  4. package/esm/PluginLoader.js +52 -8
  5. package/esm/PluginManager.d.ts +70 -17
  6. package/esm/PluginManager.js +151 -20
  7. package/esm/ReExports/modules.js +3 -53
  8. package/esm/ReExports/publicUtil.d.ts +1 -1
  9. package/esm/ReExports/publicUtil.js +1 -1
  10. package/esm/ReExports/sharedModules.d.ts +96 -0
  11. package/esm/ReExports/sharedModules.js +59 -0
  12. package/esm/ReExports/uiStub.d.ts +1 -0
  13. package/esm/ReExports/uiStub.js +13 -0
  14. package/esm/ReExports/workerModules.d.ts +2 -0
  15. package/esm/ReExports/workerModules.js +10 -0
  16. package/esm/configuration/configurationSchema.d.ts +5 -38
  17. package/esm/configuration/configurationSchema.js +53 -40
  18. package/esm/configuration/promotableDefaults.js +12 -6
  19. package/esm/configuration/promotableResolve.d.ts +1 -1
  20. package/esm/configuration/promotableResolve.js +1 -1
  21. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.d.ts +12 -0
  22. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +39 -0
  23. package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +29 -0
  24. package/esm/data_adapters/BaseAdapter/featureDensity.js +17 -0
  25. package/esm/data_adapters/BaseAdapter/index.d.ts +1 -0
  26. package/esm/data_adapters/BaseAdapter/index.js +1 -0
  27. package/esm/data_adapters/BaseAdapter/stats.d.ts +33 -1
  28. package/esm/data_adapters/BaseAdapter/stats.js +52 -13
  29. package/esm/data_adapters/BaseAdapter/types.d.ts +1 -0
  30. package/esm/pluggableElementTypes/DisplayType.d.ts +9 -10
  31. package/esm/pluggableElementTypes/DisplayType.js +43 -8
  32. package/esm/pluggableElementTypes/ViewType.d.ts +25 -2
  33. package/esm/pluggableElementTypes/ViewType.js +75 -2
  34. package/esm/pluggableElementTypes/extendElementType.js +8 -3
  35. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +41 -0
  36. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +74 -1
  37. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +21 -0
  38. package/esm/pluggableElementTypes/models/BaseTrackModel.js +37 -2
  39. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +49 -0
  40. package/esm/pluggableElementTypes/models/BaseViewModel.js +39 -0
  41. package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +10 -7
  42. package/esm/pluggableElementTypes/models/assertDisplayContract.js +23 -30
  43. package/esm/rpc/RpcRegistry.d.ts +10 -0
  44. package/esm/rpc/byteBudget.d.ts +9 -0
  45. package/esm/rpc/coreRpcMethods.d.ts +1 -0
  46. package/esm/rpc/coreRpcMethods.js +1 -0
  47. package/esm/rpc/methods/CoreGetFeatureDensity.d.ts +11 -0
  48. package/esm/rpc/methods/CoreGetFeatureDensity.js +22 -0
  49. package/esm/svg/svgReady.d.ts +1 -0
  50. package/esm/ui/CascadingMenu.js +30 -8
  51. package/esm/ui/LoadingOverlay.d.ts +14 -0
  52. package/esm/ui/LoadingOverlay.js +1 -1
  53. package/esm/ui/MenuTypes.d.ts +11 -2
  54. package/esm/ui/MenuTypes.js +3 -0
  55. package/esm/ui/PaletteContext.d.ts +11 -4
  56. package/esm/ui/PaletteContext.js +34 -5
  57. package/esm/ui/PinAdornment.js +12 -6
  58. package/esm/ui/index.d.ts +1 -1
  59. package/esm/ui/index.js +1 -1
  60. package/esm/ui/launchViewMenu.js +7 -1
  61. package/esm/ui/legendMenuItem.d.ts +5 -5
  62. package/esm/ui/legendMenuItem.js +5 -5
  63. package/esm/ui/makeSizeMenu.js +1 -1
  64. package/esm/ui/menuItems.d.ts +1 -1
  65. package/esm/ui/menuItems.js +1 -1
  66. package/esm/ui/promotableMenuItems.js +8 -6
  67. package/esm/ui/promotablePinCoverage.js +2 -1
  68. package/esm/ui/toggleMenuItems.d.ts +1 -3
  69. package/esm/util/addAndShowTrack.d.ts +1 -1
  70. package/esm/util/addAndShowTrack.js +5 -1
  71. package/esm/util/addTrackFromWidget.js +2 -2
  72. package/esm/util/cacheBudgets.d.ts +22 -19
  73. package/esm/util/cacheBudgets.js +22 -19
  74. package/esm/util/createStopTokenRotation.d.ts +1 -0
  75. package/esm/util/createStopTokenRotation.js +7 -27
  76. package/esm/util/fetchContext.d.ts +2 -2
  77. package/esm/util/fetchContext.js +2 -2
  78. package/esm/util/fetchPhases.d.ts +9 -9
  79. package/esm/util/hooks.d.ts +8 -0
  80. package/esm/util/hooks.js +27 -0
  81. package/esm/util/index.d.ts +2 -3
  82. package/esm/util/index.js +2 -3
  83. package/esm/util/installFetch.d.ts +49 -23
  84. package/esm/util/installFetch.js +49 -21
  85. package/esm/util/installInitAutorun.d.ts +8 -8
  86. package/esm/util/installInitAutorun.js +15 -15
  87. package/esm/util/lazyAfterAttach.d.ts +13 -0
  88. package/esm/util/lazyAfterAttach.js +31 -0
  89. package/esm/util/legendCandidates.d.ts +50 -0
  90. package/esm/util/legendCandidates.js +87 -0
  91. package/esm/util/mstUtils.d.ts +12 -1
  92. package/esm/util/mstUtils.js +20 -2
  93. package/esm/util/pluginStore.d.ts +1 -0
  94. package/esm/util/pluginStore.js +8 -1
  95. package/esm/util/range.d.ts +13 -9
  96. package/esm/util/range.js +17 -8
  97. package/esm/util/rxjs.d.ts +16 -0
  98. package/esm/util/rxjs.js +49 -0
  99. package/esm/util/tabix.d.ts +10 -4
  100. package/esm/util/tabix.js +11 -5
  101. package/esm/util/tracks.d.ts +48 -0
  102. package/esm/util/tracks.js +192 -66
  103. package/esm/util/types/index.d.ts +48 -5
  104. package/esm/util/types/index.js +3 -0
  105. package/esm/util/unknownSnapshotKeys.d.ts +36 -0
  106. package/esm/util/unknownSnapshotKeys.js +69 -0
  107. package/esm/util/viewStatus.d.ts +23 -0
  108. package/esm/util/viewStatus.js +17 -0
  109. package/esm/util/whenViewSettled.d.ts +17 -4
  110. package/esm/util/whenViewSettled.js +19 -6
  111. package/esm/util/withLaunchInput.d.ts +123 -0
  112. package/esm/util/withLaunchInput.js +228 -0
  113. package/package.json +12 -9
  114. package/esm/util/applyInitSettings.d.ts +0 -79
  115. package/esm/util/applyInitSettings.js +0 -91
@@ -0,0 +1,87 @@
1
+ // How many distinct colors a derived key may name and still BE a key. Past this
2
+ // the data is not a categorical vocabulary (a track keyed by unique per-feature
3
+ // names), and a list of every feature is the thing a key exists instead of.
4
+ //
5
+ // A different question from `legendIsReadable`'s lower bar, which asks whether a
6
+ // key a display already has is worth the rows it costs.
7
+ export const MAX_LEGEND_ENTRIES = 30;
8
+ // Bound on the candidate list one region ships. Far above MAX_LEGEND_ENTRIES, so
9
+ // a vocabulary that is categorical at all crosses whole, and a track with a name
10
+ // per feature ships a prefix that reads the same way it would have.
11
+ export const MAX_LEGEND_CANDIDATES = 1024;
12
+ /**
13
+ * Accumulator for the distinct (row, name, color) combinations a packer walks
14
+ * past, in first-seen order and bounded — the small list `unionLegendCandidates`
15
+ * derives a key from, so the main thread never re-walks the features. Fed scalar
16
+ * by scalar, from the loop that already holds the three values.
17
+ *
18
+ * Deduped on the whole triple rather than on (row, color): the union takes the
19
+ * first NAME it sees for a color, so where that name is already spoken for, the
20
+ * second name on that color has to still be in the list to carry it.
21
+ *
22
+ * A nameless feature contributes nothing, which is the union's rule too — so a
23
+ * track whose features have no names ships an empty list rather than one entry
24
+ * per feature.
25
+ */
26
+ export function createLegendCandidateCollector(maxCandidates = MAX_LEGEND_CANDIDATES) {
27
+ const candidates = [];
28
+ const colorsSeen = new Map();
29
+ return {
30
+ candidates,
31
+ add(rowIndex, label, color) {
32
+ if (label !== '' && candidates.length < maxCandidates) {
33
+ let byLabel = colorsSeen.get(rowIndex);
34
+ if (byLabel === undefined) {
35
+ byLabel = new Map();
36
+ colorsSeen.set(rowIndex, byLabel);
37
+ }
38
+ let colors = byLabel.get(label);
39
+ if (colors === undefined) {
40
+ colors = new Set();
41
+ byLabel.set(label, colors);
42
+ }
43
+ if (!colors.has(color)) {
44
+ colors.add(color);
45
+ candidates.push({ rowIndex, label, color });
46
+ }
47
+ }
48
+ },
49
+ };
50
+ }
51
+ /**
52
+ * The categorical color key over any number of packed regions: one entry per
53
+ * distinct color, labeled by the first name seen in that color, in first-seen
54
+ * order.
55
+ *
56
+ * Keyed by color rather than by name, so each key row is 1:1 with a color — a
57
+ * row is normally a toggle and hiding a category hides features BY color, so two
58
+ * names sharing one color must collapse to a single row. A name reused across
59
+ * two colors keeps its first-seen color.
60
+ *
61
+ * `[]` when the data carries no categorical signal: nothing named, or more than
62
+ * `maxEntries` distinct colors.
63
+ *
64
+ * `regions` is consumed lazily, so a caller able to answer without reading any
65
+ * of them can pass a generator and have none of them touched.
66
+ */
67
+ export function unionLegendCandidates(regions, resolve, maxEntries = MAX_LEGEND_ENTRIES) {
68
+ const entries = [];
69
+ const seenColors = new Set();
70
+ const seenLabels = new Set();
71
+ for (const region of regions) {
72
+ const { candidates, rowPaintsCandidateColor } = resolve(region);
73
+ for (const { rowIndex, label, color } of candidates) {
74
+ if (rowPaintsCandidateColor(rowIndex) &&
75
+ !seenLabels.has(label) &&
76
+ !seenColors.has(color)) {
77
+ seenLabels.add(label);
78
+ seenColors.add(color);
79
+ entries.push({ label, color });
80
+ if (entries.length > maxEntries) {
81
+ return [];
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return entries;
87
+ }
@@ -1,5 +1,5 @@
1
1
  import type PluginManager from '../PluginManager.ts';
2
- import type { AbstractDisplayModel, AbstractSessionModel, AbstractTrackModel, AbstractViewModel } from './types/index.ts';
2
+ import type { AbstractDisplayModel, AbstractSessionModel, AbstractViewContainer, AbstractTrackModel, AbstractViewModel } from './types/index.ts';
3
3
  import type { AssemblyHost, RenderingServices } from './types/renderingServices.ts';
4
4
  import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
5
5
  export { findParentThat, findParentThatIs, getRpcSessionId, } from './parentWalk.ts';
@@ -85,5 +85,16 @@ export declare function canonicalizeViewRefName(node: IAnyStateTreeNode, refName
85
85
  export declare function getEnv(obj: IAnyStateTreeNode): {
86
86
  pluginManager: PluginManager;
87
87
  };
88
+ /**
89
+ * #api core/util
90
+ * `addOrReplaceView` for view types whose state model may be lazily loaded;
91
+ * the synchronous `addOrReplaceView` requires it loaded already.
92
+ */
93
+ export declare function launchOrReplaceView(args: {
94
+ session: AbstractViewContainer;
95
+ typeName: string;
96
+ initialState?: Record<string, unknown>;
97
+ replacing?: AbstractViewModel;
98
+ }): Promise<AbstractViewModel>;
88
99
  export declare function hashCode(str: string): number;
89
100
  export declare function objectHash(obj: object): string;
@@ -1,6 +1,6 @@
1
- import { getEnv as getEnvMST } from '@jbrowse/mobx-state-tree';
1
+ import { getEnv as getEnvMST, isStateTreeNode } from '@jbrowse/mobx-state-tree';
2
2
  import { cachedParent, findParentThatIs } from './parentWalk.js';
3
- import { isDisplayModel, isSessionModel, isTrackModel, isViewModel, } from './types/index.js';
3
+ import { addOrReplaceView, isDisplayModel, isSessionModel, isTrackModel, isViewModel, } from './types/index.js';
4
4
  export { findParentThat, findParentThatIs, getRpcSessionId, } from './parentWalk.js';
5
5
  const containingDisplayCache = new WeakMap();
6
6
  const containingTrackCache = new WeakMap();
@@ -105,6 +105,24 @@ export function canonicalizeViewRefName(node, refName) {
105
105
  export function getEnv(obj) {
106
106
  return getEnvMST(obj);
107
107
  }
108
+ /**
109
+ * #api core/util
110
+ * `addOrReplaceView` for view types whose state model may be lazily loaded;
111
+ * the synchronous `addOrReplaceView` requires it loaded already.
112
+ */
113
+ export async function launchOrReplaceView(args) {
114
+ // a test's fake session is not an MST node; addView's own guard is the
115
+ // error path there
116
+ if (isStateTreeNode(args.session)) {
117
+ const { pluginManager } = getEnvMST(args.session);
118
+ await pluginManager?.getViewType(args.typeName).loadStateModel();
119
+ // the displays of the tracks a launch opens with are dynamic imports too
120
+ await pluginManager?.preloadSessionTypes({
121
+ views: [{ type: args.typeName, ...args.initialState }],
122
+ });
123
+ }
124
+ return addOrReplaceView(args);
125
+ }
108
126
  export function hashCode(str) {
109
127
  let hash = 0;
110
128
  if (str.length === 0) {
@@ -34,6 +34,7 @@ export interface PluginUpdate {
34
34
  name: string;
35
35
  definition: PluginDefinition;
36
36
  }
37
+ export declare function releaseVersion(jbrowseVersion: string): string;
37
38
  export declare function resolvePlugin(plugin: JBrowsePlugin, jbrowseVersion: string): ResolvedPlugin;
38
39
  export declare function installedVersionFromUrl(url: string | undefined, packageName: string | undefined): string | undefined;
39
40
  /**
@@ -37,6 +37,13 @@ export function installablePlugins(plugins, isElectron) {
37
37
  (isElectron ||
38
38
  publishedBuilds(plugin).some(b => b.esmUrl ?? b.url ?? b.umdUrl)));
39
39
  }
40
+ // A prerelease host is its release for compatibility purposes. Under semver
41
+ // 5.0.0-beta.1 sorts below 5.0.0, so a range written to keep a plugin off v5
42
+ // (`<5.0.0`) would serve it to every beta host and a v5-only range (`>=5.0.0`)
43
+ // would hide it from them. Store ranges are written against release versions.
44
+ export function releaseVersion(jbrowseVersion) {
45
+ return jbrowseVersion.split('-')[0];
46
+ }
40
47
  // `*` (and empty) mean "any JBrowse version"; compare-versions throws on those,
41
48
  // so handle them directly. A malformed range simply fails to match rather than
42
49
  // breaking the store UI — producer-side validation is the place to reject those.
@@ -44,7 +51,7 @@ function rangeMatches(jbrowseVersion, range) {
44
51
  let matched = range === '*' || range === '';
45
52
  if (!matched) {
46
53
  try {
47
- matched = satisfies(jbrowseVersion, range);
54
+ matched = satisfies(releaseVersion(jbrowseVersion), range);
48
55
  }
49
56
  catch {
50
57
  matched = false;
@@ -20,9 +20,15 @@ export declare function doesIntersect2(left1: number, right1: number, left2: num
20
20
  /**
21
21
  * Compute the expanded fetch range a tabix adapter must "redispatch" to when
22
22
  * features found in a query extend past the requested window. Returns the union
23
- * of the query with the bounds of every feature whose type is not in
24
- * `dontRedispatchSet`, or `undefined` when nothing extends past the query (no
25
- * redispatch needed).
23
+ * of the query with the bounds of every line `expands` admits, or `undefined`
24
+ * when nothing extends past the query (no redispatch needed).
25
+ *
26
+ * **A predicate, not a set of types**, because the useful question is not what a
27
+ * record is called. The bound exists to reach a record's *children*, so what
28
+ * disqualifies a record is having none — and in GFF3 that is answerable exactly,
29
+ * from whether the record carries an `ID` for a `Parent` to reference. A type
30
+ * blocklist can only ever approximate it, and it is the caller's format that
31
+ * knows how: this file reads no column but the coordinates.
26
32
  *
27
33
  * Feature coordinates arrive interbase, matching the query: @gmod/tabix applies
28
34
  * the index's coordinate offset before invoking the line callback, so a GFF/GTF
@@ -30,15 +36,13 @@ export declare function doesIntersect2(left1: number, right1: number, left2: num
30
36
  *
31
37
  * Seeding the accumulator with the query bounds is what makes the result a
32
38
  * union rather than just the feature bounds. A redispatch narrower than the
33
- * original window would drop a dontRedispatch-typed feature that the first
34
- * fetch found inside the query but that falls outside the expanded range, since
35
- * those types are deliberately excluded from the bounds.
39
+ * original window would drop an excluded line that the first fetch found inside
40
+ * the query but that falls outside the expanded range.
36
41
  */
37
- export declare function calculateRedispatchRange(features: {
42
+ export declare function calculateRedispatchRange<T extends {
38
43
  start: number;
39
44
  end: number;
40
- type: string;
41
- }[], dontRedispatchSet: Set<string>, queryStart: number, queryEnd: number): {
45
+ }>(features: T[], expands: (feature: T) => boolean, queryStart: number, queryEnd: number): {
42
46
  start: number;
43
47
  end: number;
44
48
  } | undefined;
package/esm/util/range.js CHANGED
@@ -45,9 +45,15 @@ export function doesIntersect2(left1, right1, left2, right2) {
45
45
  /**
46
46
  * Compute the expanded fetch range a tabix adapter must "redispatch" to when
47
47
  * features found in a query extend past the requested window. Returns the union
48
- * of the query with the bounds of every feature whose type is not in
49
- * `dontRedispatchSet`, or `undefined` when nothing extends past the query (no
50
- * redispatch needed).
48
+ * of the query with the bounds of every line `expands` admits, or `undefined`
49
+ * when nothing extends past the query (no redispatch needed).
50
+ *
51
+ * **A predicate, not a set of types**, because the useful question is not what a
52
+ * record is called. The bound exists to reach a record's *children*, so what
53
+ * disqualifies a record is having none — and in GFF3 that is answerable exactly,
54
+ * from whether the record carries an `ID` for a `Parent` to reference. A type
55
+ * blocklist can only ever approximate it, and it is the caller's format that
56
+ * knows how: this file reads no column but the coordinates.
51
57
  *
52
58
  * Feature coordinates arrive interbase, matching the query: @gmod/tabix applies
53
59
  * the index's coordinate offset before invoking the line callback, so a GFF/GTF
@@ -55,15 +61,18 @@ export function doesIntersect2(left1, right1, left2, right2) {
55
61
  *
56
62
  * Seeding the accumulator with the query bounds is what makes the result a
57
63
  * union rather than just the feature bounds. A redispatch narrower than the
58
- * original window would drop a dontRedispatch-typed feature that the first
59
- * fetch found inside the query but that falls outside the expanded range, since
60
- * those types are deliberately excluded from the bounds.
64
+ * original window would drop an excluded line that the first fetch found inside
65
+ * the query but that falls outside the expanded range.
61
66
  */
62
- export function calculateRedispatchRange(features, dontRedispatchSet, queryStart, queryEnd) {
67
+ export function calculateRedispatchRange(features, expands, queryStart, queryEnd) {
63
68
  let minStart = queryStart;
64
69
  let maxEnd = queryEnd;
70
+ // bounds first: only a line reaching past the current bound can widen it, so
71
+ // the predicate, which may scan the line's text, runs on those few lines and
72
+ // not on every line in the window
65
73
  for (const feature of features) {
66
- if (!dontRedispatchSet.has(feature.type)) {
74
+ if ((feature.start < minStart || feature.end > maxEnd) &&
75
+ expands(feature)) {
67
76
  if (feature.start < minStart) {
68
77
  minStart = feature.start;
69
78
  }
@@ -15,3 +15,19 @@ import type { Observer } from 'rxjs';
15
15
  * @param func - observer function, could be async
16
16
  */
17
17
  export declare function ObservableCreate<T>(func: (arg: Observer<T>) => void | Promise<void>, stopToken?: StopToken): Observable<T>;
18
+ /**
19
+ * Subscribe to an observable and resolve when it completes, running `onNext` per
20
+ * item as it arrives — the streaming shape for parsing items out of a feature
21
+ * stream rather than collecting them first.
22
+ *
23
+ * **`onNext` throwing rejects the promise.** rxjs does not do that on its own:
24
+ * an exception out of a `next` handler goes to its global unhandled-error hook,
25
+ * so the subscriber keeps being fed and `complete` still fires — a bare promise
26
+ * around that resolves as *success* with the items silently missing. Beside
27
+ * `ObservableCreate` because it is the other half of the pattern: the adapter
28
+ * wraps its body in that, the RPC `await`s this, so a per-item parse failure
29
+ * surfaces as a track error instead of a blank-but-loaded track.
30
+ *
31
+ * The source is unsubscribed on the first throw.
32
+ */
33
+ export declare function subscribeToObservable<T>(observable: Observable<T>, onNext: (item: T) => void): Promise<void>;
package/esm/util/rxjs.js CHANGED
@@ -43,3 +43,52 @@ export function ObservableCreate(func, stopToken) {
43
43
  };
44
44
  });
45
45
  }
46
+ /**
47
+ * Subscribe to an observable and resolve when it completes, running `onNext` per
48
+ * item as it arrives — the streaming shape for parsing items out of a feature
49
+ * stream rather than collecting them first.
50
+ *
51
+ * **`onNext` throwing rejects the promise.** rxjs does not do that on its own:
52
+ * an exception out of a `next` handler goes to its global unhandled-error hook,
53
+ * so the subscriber keeps being fed and `complete` still fires — a bare promise
54
+ * around that resolves as *success* with the items silently missing. Beside
55
+ * `ObservableCreate` because it is the other half of the pattern: the adapter
56
+ * wraps its body in that, the RPC `await`s this, so a per-item parse failure
57
+ * surfaces as a track error instead of a blank-but-loaded track.
58
+ *
59
+ * The source is unsubscribed on the first throw.
60
+ */
61
+ export function subscribeToObservable(observable, onNext) {
62
+ return new Promise((resolve, reject) => {
63
+ // A box rather than two bare `let`s: oxlint narrows a flag assigned only
64
+ // inside a closure to its initializer and reports the reads as always-false.
65
+ // `subscription` is genuinely still undefined during a synchronous source's
66
+ // first emissions, which is why it is read optionally below.
67
+ const state = {
68
+ failed: false,
69
+ };
70
+ state.subscription = observable.subscribe({
71
+ next: item => {
72
+ if (!state.failed) {
73
+ try {
74
+ onNext(item);
75
+ }
76
+ catch (e) {
77
+ state.failed = true;
78
+ // A parser throws an Error in every real case here; the wrap is for
79
+ // the rule that a rejection reason must be one, and keeps whatever
80
+ // was actually thrown as the `cause`.
81
+ reject(e instanceof Error ? e : new Error(String(e), { cause: e }));
82
+ state.subscription?.unsubscribe();
83
+ }
84
+ }
85
+ },
86
+ error: reject,
87
+ complete: () => {
88
+ if (!state.failed) {
89
+ resolve();
90
+ }
91
+ },
92
+ });
93
+ });
94
+ }
@@ -45,9 +45,15 @@ export declare function readTabixLines(gff: TabixLineSource, refName: string, st
45
45
  * Read a region's tabix lines, then — if any feature found there extends past
46
46
  * the query — read the overhang on either side, so a parent line's children (a
47
47
  * gene's exons, a transcript's CDS) outside the original window are pulled in
48
- * and the parent/child tree resolves fully. Types in `dontRedispatchSet` are
49
- * excluded from the bounds, so one chromosome-spanning record can't force a
50
- * whole-chromosome refetch.
48
+ * and the parent/child tree resolves fully. `expands` decides which lines widen
49
+ * the bound, so one chromosome-spanning record can't force a whole-chromosome
50
+ * refetch; {@link calculateRedispatchRange} says why that is a predicate.
51
+ *
52
+ * **There is one control, and skipping the expansion is a value of it**: an
53
+ * `expands` that admits nothing reads the query and stops. A caller that wants
54
+ * that says so to its adapter, which is the layer that knows whether its format
55
+ * can honour it — see `Gff3TabixAdapter`, and `GtfTabixAdapter` for a format
56
+ * that cannot.
51
57
  *
52
58
  * **The flanks, not the union.** Re-reading `[minStart, maxEnd]` returns
53
59
  * everything the first read already had — a second full read of the query range
@@ -76,7 +82,7 @@ export declare function readTabixLinesRedispatched(file: TabixLineSource, query:
76
82
  refName: string;
77
83
  start: number;
78
84
  end: number;
79
- }, dontRedispatchSet: Set<string>, opts?: {
85
+ }, expands: (line: TabixLine) => boolean, opts?: {
80
86
  statusCallback?: StatusCallback;
81
87
  stopToken?: StopToken;
82
88
  }): Promise<TabixLine[]>;
package/esm/util/tabix.js CHANGED
@@ -44,9 +44,15 @@ export function readTabixLines(gff, refName, start, end, statusCallback, stopTok
44
44
  * Read a region's tabix lines, then — if any feature found there extends past
45
45
  * the query — read the overhang on either side, so a parent line's children (a
46
46
  * gene's exons, a transcript's CDS) outside the original window are pulled in
47
- * and the parent/child tree resolves fully. Types in `dontRedispatchSet` are
48
- * excluded from the bounds, so one chromosome-spanning record can't force a
49
- * whole-chromosome refetch.
47
+ * and the parent/child tree resolves fully. `expands` decides which lines widen
48
+ * the bound, so one chromosome-spanning record can't force a whole-chromosome
49
+ * refetch; {@link calculateRedispatchRange} says why that is a predicate.
50
+ *
51
+ * **There is one control, and skipping the expansion is a value of it**: an
52
+ * `expands` that admits nothing reads the query and stops. A caller that wants
53
+ * that says so to its adapter, which is the layer that knows whether its format
54
+ * can honour it — see `Gff3TabixAdapter`, and `GtfTabixAdapter` for a format
55
+ * that cannot.
50
56
  *
51
57
  * **The flanks, not the union.** Re-reading `[minStart, maxEnd]` returns
52
58
  * everything the first read already had — a second full read of the query range
@@ -74,13 +80,13 @@ export function readTabixLines(gff, refName, start, end, statusCallback, stopTok
74
80
  export async function readTabixLinesRedispatched(file,
75
81
  // the region's locus only — kept structural, like TabixLineSource above, so
76
82
  // this module stays free of the `types` barrel and its MST models
77
- query, dontRedispatchSet, opts = {}) {
83
+ query, expands, opts = {}) {
78
84
  const { statusCallback, stopToken } = opts;
79
85
  // `cb` is not optional: each read decides where it reports, and the flanks
80
86
  // report somewhere the first read does not
81
87
  const read = (start, end, cb) => readTabixLines(file, query.refName, start, end, cb, stopToken);
82
88
  const lines = await read(query.start, query.end, statusCallback);
83
- const redispatch = calculateRedispatchRange(lines, dontRedispatchSet, query.start, query.end);
89
+ const redispatch = calculateRedispatchRange(lines, expands, query.start, query.end);
84
90
  if (redispatch) {
85
91
  const slot = createStatusFanOut(statusCallback);
86
92
  const flanks = await Promise.all([
@@ -3,6 +3,7 @@ export { getRpcSessionId } from './parentWalk.ts';
3
3
  import type PluginManager from '../PluginManager.ts';
4
4
  import type { AnyConfigurationModel } from '../configuration/index.ts';
5
5
  import type { BlobLocation, FileHandleLocation, FileLocation, PreFileLocation } from './types/data.ts';
6
+ import type { AbstractViewContainer } from './types/index.ts';
6
7
  import type { IAnyStateTreeNode, IAnyType, IStateTreeNode, Instance, types } from '@jbrowse/mobx-state-tree';
7
8
  export declare function getTrackAssemblyNames(track: IStateTreeNode & {
8
9
  configuration: AnyConfigurationModel;
@@ -242,6 +243,23 @@ export declare function guessTrackConf(input: string | LooseTrackInput, pluginMa
242
243
  assemblyNames: string[];
243
244
  adapter: AdapterConfig;
244
245
  };
246
+ /**
247
+ * {@link guessTrackConf} over an already-built {@link FileLocation}, for a
248
+ * caller holding a location the loose `{ uri }` vocabulary cannot spell — a
249
+ * desktop local path. Kept separate so `LooseTrackInput` stays URI-only: it
250
+ * sits on the track-config union and the root config's `tracks`, where a
251
+ * local path would be a config a web deployment could not open.
252
+ *
253
+ * The `trackId` is a content hash of the adapter, not a timestamp, so adding
254
+ * the same file twice is idempotent — `addToSession` dedupes on it.
255
+ */
256
+ export declare function guessTrackConfForLocation(file: FileLocation, index: FileLocation | undefined, pluginManager: PluginManager, assemblyName?: string): {
257
+ trackId: string;
258
+ type: string;
259
+ name: string;
260
+ assemblyNames: string[];
261
+ adapter: AdapterConfig;
262
+ };
245
263
  /**
246
264
  * Whether a track snapshot is the loose `{ uri, ... }` form — a data file with
247
265
  * no `adapter` — that {@link expandLooseTrackConfig} expands.
@@ -294,6 +312,7 @@ interface MinimalTrack extends IAnyType {
294
312
  interface GenericView {
295
313
  type: string;
296
314
  tracks: MSTArray<MinimalTrack>;
315
+ showTrack?: (trackId: string, initialSnapshot?: object, displayInitialSnapshot?: DisplayInitialSnapshot, inlineConf?: Record<string, unknown>) => unknown;
297
316
  }
298
317
  /**
299
318
  * The display-snapshot argument of `showTrackGeneric`, exported so a view's own
@@ -403,6 +422,8 @@ export declare function stripTrackIds(tracks: TrackSnapshot[]): {
403
422
  }[];
404
423
  export declare function hideTrackGeneric(self: GenericView, trackId: string): boolean;
405
424
  export declare function toggleTrackGeneric(self: GenericView, trackId: string): boolean;
425
+ export declare function launchTrackGeneric(self: GenericView, trackId: string, initialSnapshot?: object, displayInitialSnapshot?: DisplayInitialSnapshot, inlineConf?: Record<string, unknown>): Promise<any>;
426
+ export declare function launchToggleTrackGeneric(self: GenericView, trackId: string): Promise<boolean>;
406
427
  /**
407
428
  * Every track config the session can show, connection-supplied ones included.
408
429
  *
@@ -424,3 +445,30 @@ export declare function allSessionTracks(session: {
424
445
  setSubschema(slotName: string, data: Record<string, unknown>): any;
425
446
  setSlot(slotName: string, value: unknown): void;
426
447
  } & IStateTreeNode<import("../configuration/types.ts").AnyConfigurationSchemaType>)[];
448
+ /**
449
+ * The session's annotation for `assemblyName`, as trackIds: what a view opened
450
+ * on another genome starts with. Only FeatureTracks, not everything configured
451
+ * for the assembly — an alignments track turned on behind the user's back
452
+ * fetches data nobody asked for, and a FeatureTrack over a few kb is one tabix
453
+ * range query.
454
+ */
455
+ export declare function annotationTrackIds(session: {
456
+ tracks: AnyConfigurationModel[];
457
+ connectionInstances?: {
458
+ tracks: AnyConfigurationModel[];
459
+ }[];
460
+ assemblyManager: AssemblyNameResolver;
461
+ }, assemblyName: string): string[];
462
+ /**
463
+ * One assembly on its own in a linear genome view, at `loc`. Keyed on `id`:
464
+ * following several alignments to one genome re-navigates the view rather than
465
+ * stacking a new one each time.
466
+ */
467
+ export declare function openAssemblyInLinearView({ session, id, assemblyName, loc, tracks, }: {
468
+ session: AbstractViewContainer;
469
+ id: string;
470
+ assemblyName: string;
471
+ loc: string;
472
+ tracks: TrackInit[];
473
+ }): Promise<void>;
474
+ export declare function openMateLabel(assemblyName: string): string;