@jbrowse/product-core 4.3.0 → 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 (137) hide show
  1. package/esm/RootModel/BaseRootModel.d.ts +629 -304
  2. package/esm/RootModel/BaseRootModel.js +139 -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 +68 -0
  8. package/esm/RootModel/PreferencesConfig.js +76 -0
  9. package/esm/RootModel/createConfigModel.d.ts +240 -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 +312 -11
  16. package/esm/Session/BaseSession.js +458 -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 +1281 -1128
  22. package/esm/Session/MultipleViews.js +396 -64
  23. package/esm/Session/Preferences.d.ts +1667 -0
  24. package/esm/Session/Preferences.js +59 -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 +1277 -1138
  28. package/esm/Session/SessionTracks.js +540 -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 +1171 -1117
  36. package/esm/Session/Tracks.js +183 -20
  37. package/esm/Session/assertSessionModel.d.ts +21 -0
  38. package/esm/Session/assertSessionModel.js +1 -0
  39. package/esm/Session/developerMode.d.ts +7 -0
  40. package/esm/Session/developerMode.js +43 -0
  41. package/esm/Session/finalizeSession.d.ts +16 -0
  42. package/esm/Session/finalizeSession.js +29 -0
  43. package/esm/Session/index.d.ts +22 -10
  44. package/esm/Session/index.js +13 -10
  45. package/esm/Session/readdedTrackConf.d.ts +8 -0
  46. package/esm/Session/readdedTrackConf.js +25 -0
  47. package/esm/Session/shareableSnapshot.d.ts +77 -0
  48. package/esm/Session/shareableSnapshot.js +196 -0
  49. package/esm/Session/temporaryAssemblyTracks.d.ts +24 -0
  50. package/esm/Session/temporaryAssemblyTracks.js +37 -0
  51. package/esm/assertExtends.d.ts +55 -0
  52. package/esm/assertExtends.js +1 -0
  53. package/esm/controllerTracks.d.ts +56 -0
  54. package/esm/controllerTracks.js +90 -0
  55. package/esm/destroyViewState.d.ts +21 -0
  56. package/esm/destroyViewState.js +29 -0
  57. package/esm/index.d.ts +31 -4
  58. package/esm/index.js +31 -4
  59. package/esm/loadPlugins.d.ts +33 -0
  60. package/esm/loadPlugins.js +41 -0
  61. package/esm/localFiles.d.ts +74 -0
  62. package/esm/localFiles.js +156 -0
  63. package/esm/observeSession.d.ts +56 -0
  64. package/esm/observeSession.js +91 -0
  65. package/esm/pluginInput.d.ts +32 -0
  66. package/esm/pluginInput.js +19 -0
  67. package/esm/pruneUnbuildableNodes.d.ts +109 -0
  68. package/esm/pruneUnbuildableNodes.js +568 -0
  69. package/esm/resolveAssemblies.d.ts +44 -0
  70. package/esm/resolveAssemblies.js +55 -0
  71. package/esm/rpcWorker.d.ts +26 -0
  72. package/esm/rpcWorker.js +98 -16
  73. package/esm/scheduleDetachedDestroy.d.ts +18 -0
  74. package/esm/scheduleDetachedDestroy.js +24 -0
  75. package/esm/sessionMigrations/index.d.ts +53 -0
  76. package/esm/sessionMigrations/index.js +432 -0
  77. package/esm/sessionUrl.d.ts +41 -0
  78. package/esm/sessionUrl.js +53 -0
  79. package/esm/sessionUtils.d.ts +69 -0
  80. package/esm/sessionUtils.js +613 -0
  81. package/esm/siblingCast.d.ts +14 -0
  82. package/esm/siblingCast.js +16 -0
  83. package/esm/snapshotUtils.d.ts +8 -0
  84. package/esm/snapshotUtils.js +12 -0
  85. package/esm/ui/AboutDialog.d.ts +3 -6
  86. package/esm/ui/AboutDialog.js +4 -4
  87. package/esm/ui/AboutDialogContents.d.ts +2 -6
  88. package/esm/ui/AboutDialogContents.js +34 -13
  89. package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
  90. package/esm/ui/AssemblyInfoPanel.js +69 -0
  91. package/esm/ui/DisplayDefaultsSection.d.ts +36 -0
  92. package/esm/ui/DisplayDefaultsSection.js +65 -0
  93. package/esm/ui/Drawer.d.ts +21 -0
  94. package/esm/ui/Drawer.js +60 -0
  95. package/esm/ui/DrawerControls.d.ts +5 -0
  96. package/esm/ui/DrawerControls.js +28 -0
  97. package/esm/ui/DrawerHeader.d.ts +6 -0
  98. package/esm/ui/DrawerHeader.js +36 -0
  99. package/esm/ui/DrawerWidget.d.ts +5 -0
  100. package/esm/ui/DrawerWidget.js +12 -0
  101. package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
  102. package/esm/ui/DrawerWidgetSelector.js +40 -0
  103. package/esm/ui/FileInfoPanel.d.ts +3 -6
  104. package/esm/ui/FileInfoPanel.js +35 -26
  105. package/esm/ui/HeaderButtons.d.ts +7 -1
  106. package/esm/ui/HeaderButtons.js +23 -18
  107. package/esm/ui/ModalWidget.d.ts +12 -0
  108. package/esm/ui/ModalWidget.js +33 -0
  109. package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
  110. package/esm/ui/ModalWidgetAppBar.js +9 -0
  111. package/esm/ui/PreferencesDialog.d.ts +56 -0
  112. package/esm/ui/PreferencesDialog.js +145 -0
  113. package/esm/ui/PreferencesResetDialog.d.ts +8 -0
  114. package/esm/ui/PreferencesResetDialog.js +19 -0
  115. package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
  116. package/esm/ui/RefNameAliasesDialog.js +65 -0
  117. package/esm/ui/RefNameInfoDialog.d.ts +3 -6
  118. package/esm/ui/RefNameInfoDialog.js +57 -63
  119. package/esm/ui/WidgetBody.d.ts +18 -0
  120. package/esm/ui/WidgetBody.js +22 -0
  121. package/esm/ui/WidgetHeading.d.ts +7 -0
  122. package/esm/ui/WidgetHeading.js +7 -0
  123. package/esm/ui/drawerLayout.d.ts +18 -0
  124. package/esm/ui/drawerLayout.js +22 -0
  125. package/esm/ui/index.d.ts +13 -1
  126. package/esm/ui/index.js +12 -1
  127. package/esm/ui/util.d.ts +37 -4
  128. package/esm/ui/util.js +35 -38
  129. package/esm/useEngineLifecycle.d.ts +19 -0
  130. package/esm/useEngineLifecycle.js +44 -0
  131. package/package.json +12 -15
  132. package/esm/RootModel/FormatAbout.d.ts +0 -12
  133. package/esm/RootModel/FormatAbout.js +0 -15
  134. package/esm/RootModel/FormatDetails.d.ts +0 -24
  135. package/esm/RootModel/FormatDetails.js +0 -27
  136. package/esm/Session/DialogQueue.d.ts +0 -17
  137. package/esm/Session/DialogQueue.js +0 -31
@@ -0,0 +1,196 @@
1
+ import { getConf, getDisplayTypeDefaultChanges, } from '@jbrowse/core/configuration';
2
+ import { getContainingTrack, mergeTrackConfig, openPromotableDisplays, } from '@jbrowse/core/util';
3
+ import { getSnapshot } from '@jbrowse/mobx-state-tree';
4
+ import { asArray, isRecord } from '../snapshotUtils.js';
5
+ /**
6
+ * The session snapshot to hand to anyone else — a share link, an exported
7
+ * `session.json`, a desktop→web export. Snapshotting and baking are one call so
8
+ * the pair can't be split: a bare `getSnapshot(session)` is never a correct
9
+ * outgoing snapshot (see `bakeSessionCascades`), and three of the four
10
+ * boundaries were spelling the two steps out identically.
11
+ *
12
+ * The fourth (desktop `ExportToWebDialog`) bakes a *transformed* snapshot from
13
+ * `planWebExport`, so it calls `bakeSessionCascades` directly.
14
+ */
15
+ export function getShareableSessionSnapshot(session) {
16
+ return bakeSessionCascades(session, getSnapshot(session));
17
+ }
18
+ /**
19
+ * Everything an outgoing snapshot has to resolve because the live session
20
+ * resolves it at read time against state that is staying behind.
21
+ *
22
+ * Two such cascades, and they have nothing to do with each other — promoted
23
+ * display-type defaults are per-track rendering, workspaces intent is the
24
+ * session's layout mode. What they share is only this: both live partly in the
25
+ * sender's own browser, so a raw `getSnapshot` records neither, and the
26
+ * recipient silently resolves their own.
27
+ *
28
+ * They get one entry point rather than a call each because the failure of a
29
+ * boundary that performs half of them is invisible at that boundary — the
30
+ * export succeeds, and what is missing only shows up on someone else's screen.
31
+ * Adding a third cascade should mean editing this function, not auditing four
32
+ * call sites.
33
+ */
34
+ export function bakeSessionCascades(session, snapshot) {
35
+ const snap = bakePromotedDefaultsIntoSnapshot(session, snapshot);
36
+ bakeWorkspacesIntent(session, snap);
37
+ return snap;
38
+ }
39
+ /**
40
+ * The workspaces cascade: session property -> user preference -> admin
41
+ * `configuration.preferences.useWorkspaces`, of which only the first is in the
42
+ * snapshot.
43
+ *
44
+ * So a sender whose workspace is on because *their admin turned it on* exports
45
+ * a fully arranged `layout` with no statement that it is a workspace at all,
46
+ * and a recipient resolving their own cascade to false renders the classic
47
+ * stack: the arrangement arrives intact and invisible, with nothing to say it
48
+ * was dropped.
49
+ *
50
+ * Only the on case is baked. At `false` there is no arrangement to carry, so
51
+ * stamping it would be a sender with nothing to say overriding a recipient who
52
+ * prefers workspaces — the same line the promoted-default bake draws at a slot
53
+ * the sender was viewing at its base value.
54
+ *
55
+ * The paths that build a layout through an explicit user intent already set the
56
+ * property themselves (`setUseWorkspacesPreference`, ViewMenu's move-view-out,
57
+ * a spec `layout`), which is exactly what hides this: every arrangement made by
58
+ * a *drag* — split a cell, drag a tab into it, open a new tab — goes through
59
+ * `WorkspaceLayoutMixin` alone, which owns the tree and knows nothing about the
60
+ * preference.
61
+ *
62
+ * `effectiveUseWorkspaces` is read behind an `in` guard because
63
+ * `AbstractSessionModel` cannot name it (it lives on MultipleViewsSessionMixin,
64
+ * one layer up), so renaming that getter silently stops the bake rather than
65
+ * failing to compile — the hazard app-core/src/WorkspaceLayout/CLAUDE.md describes for
66
+ * `setPendingMove`. jbrowse-web's `sessionModel/exportLayout.test.ts` is the
67
+ * canary, since the bake needs a real session to have a cascade at all.
68
+ */
69
+ function bakeWorkspacesIntent(session, snap) {
70
+ if (snap.useWorkspaces === undefined &&
71
+ 'effectiveUseWorkspaces' in session &&
72
+ session.effectiveUseWorkspaces ===
73
+ true) {
74
+ snap.useWorkspaces = true;
75
+ }
76
+ }
77
+ /**
78
+ * `getComputedStyle` for the promotable-default cascade.
79
+ *
80
+ * A track that "follows" a session-wide promoted display-type default holds no
81
+ * value of its own — its rendered value is resolved at read time from the
82
+ * promoted display-type defaults in `preferencesOverrides`, which are personal,
83
+ * localStorage-backed, and deliberately never serialized into a shared session.
84
+ * So a raw `getSnapshot(session)` records a track as at-default even when the
85
+ * sender is looking at a promoted value, and the recipient (who lacks the sender's
86
+ * preferences) sees something different. This flattens the live cascade into a
87
+ * self-contained snapshot:
88
+ *
89
+ * Every slot an open display *inherits* from a promoted default (i.e.
90
+ * `getDisplayTypeDefaultChanges` — non-customized, differs from base) is baked
91
+ * into that track's config layer: its own config for a user-added or connection
92
+ * track, else a `trackConfigDeltas` entry against the admin base (see
93
+ * `ownTrackConfig`). So the concrete value travels with the document, and
94
+ * because it lands in the track's config the recipient reads it as *customized*
95
+ * — a customized value is the top of the cascade, so their own promoted defaults
96
+ * never get consulted for it.
97
+ *
98
+ * **What this deliberately does not cover**, and the reason there is no flag
99
+ * here: a slot the sender was viewing at its *base* value. Nothing is baked for
100
+ * it (the value equals base, and `stripDefault` drops it from the snapshot
101
+ * regardless), so a recipient who has promoted something else resolves it from
102
+ * their own cascade and sees their value. There is no value that can express "I
103
+ * deliberately saw the default" — at-base and unset are byte-identical once
104
+ * stripped. That case used to be covered by stamping a per-display
105
+ * `ignorePromotedDefaults` on every open display, which cost a second
106
+ * shape-aware walk that had to stay in step with `openPromotableDisplays` by
107
+ * hand, and permanently detached received tracks from the recipient's own pins
108
+ * until they clicked "use this default" on each one. A promoted default is
109
+ * personal and local — the same as the theme a session is viewed in, which this
110
+ * function has never baked either.
111
+ *
112
+ * The live session is untouched; a modified deep copy of `snapshot` is returned.
113
+ * Tracks the sender never opened carry no display state to resolve, so they're
114
+ * left to pick up the recipient's defaults when opened — matching "export the
115
+ * actual state of the (open) tracks".
116
+ *
117
+ * Reach is `openPromotableDisplays` — literally the same walk the cascade's own
118
+ * "apply to open tracks" uses, so the two can't drift. It recurses into a
119
+ * composite view's `views` array (breakpoint-split, the linear-comparative /
120
+ * synteny family), which holds child views rather than tracks of its own —
121
+ * `LGVSyntenyDisplay` is only reachable that way, and before the recursion a
122
+ * shared session containing one rendered differently for the recipient.
123
+ */
124
+ export function bakePromotedDefaultsIntoSnapshot(session, snapshot) {
125
+ const snap = structuredClone(snapshot);
126
+ // `openPromotableDisplays` is the one walk that decides reach — shared with
127
+ // the cascade's own "apply to open tracks", so the set this bakes and the set
128
+ // that acts on a promoted default can't drift
129
+ for (const display of openPromotableDisplays(session)) {
130
+ const changes = getDisplayTypeDefaultChanges(display);
131
+ if (changes.length > 0) {
132
+ bakeValues(snap, {
133
+ trackId: getConf(getContainingTrack(display), 'trackId'),
134
+ displayId: getConf(display, 'displayId'),
135
+ displayType: display.type,
136
+ values: Object.fromEntries(changes.map(c => [c.path[0], c.to])),
137
+ });
138
+ }
139
+ }
140
+ return snap;
141
+ }
142
+ // The track's own full config in the snapshot, when it has one: a user-added
143
+ // track's `sessionTracks` entry, or an opened connection track's persisted
144
+ // `connectionTrackConfigs` config. Everything else is an admin-base track,
145
+ // which carries only a delta.
146
+ //
147
+ // The connection case is not optional. `trackConfigDeltas` is merged over
148
+ // `jbrowse.tracks` alone, so a delta written for a connection track resolves
149
+ // nowhere — the recipient would render the base value, which is the exact
150
+ // failure the bake exists to prevent. `updateTrackConfiguration` splits on the
151
+ // same line.
152
+ function ownTrackConfig(snap, trackId) {
153
+ const sessionTrack = asArray(snap.sessionTracks).find(t => isRecord(t) && t.trackId === trackId);
154
+ const connectionEntry = isRecord(snap.connectionTrackConfigs)
155
+ ? snap.connectionTrackConfigs[trackId]
156
+ : undefined;
157
+ return isRecord(sessionTrack)
158
+ ? sessionTrack
159
+ : isRecord(connectionEntry) && isRecord(connectionEntry.config)
160
+ ? connectionEntry.config
161
+ : undefined;
162
+ }
163
+ // Bake one display's inherited values into the config layer: into the matching
164
+ // display of the track's own config when it has one, else merged as a
165
+ // `trackConfigDeltas` entry against the admin base.
166
+ function bakeValues(snap, bake) {
167
+ const partialDisplay = {
168
+ type: bake.displayType,
169
+ displayId: bake.displayId,
170
+ ...bake.values,
171
+ };
172
+ const ownConfig = ownTrackConfig(snap, bake.trackId);
173
+ if (ownConfig) {
174
+ const displays = asArray(ownConfig.displays);
175
+ const target = displays.find(d => isRecord(d) && d.displayId === bake.displayId);
176
+ if (isRecord(target)) {
177
+ Object.assign(target, bake.values);
178
+ }
179
+ else {
180
+ ownConfig.displays = [...displays, partialDisplay];
181
+ }
182
+ }
183
+ else {
184
+ const deltas = isRecord(snap.trackConfigDeltas)
185
+ ? { ...snap.trackConfigDeltas }
186
+ : {};
187
+ const existing = isRecord(deltas[bake.trackId])
188
+ ? deltas[bake.trackId]
189
+ : { trackId: bake.trackId };
190
+ deltas[bake.trackId] = mergeTrackConfig(existing, {
191
+ trackId: bake.trackId,
192
+ displays: [partialDisplay],
193
+ });
194
+ snap.trackConfigDeltas = deltas;
195
+ }
196
+ }
@@ -0,0 +1,24 @@
1
+ import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
+ /**
3
+ * Checks that a track config being written into a session or config list is one
4
+ * something can still draw tomorrow.
5
+ *
6
+ * A config naming nothing but a temporary assembly is dead the moment its view
7
+ * closes, and no list outside that view has anyone to sweep it — which is the
8
+ * whole of ADR-084. A track only one view can draw carries its config on the
9
+ * track instead (`showTrackGeneric`'s `inlineConf`), so reaching a session list
10
+ * with one is the mistake, not the cleanup afterwards.
11
+ *
12
+ * **This reports a write, not a click.** The two flows a user can drive into it
13
+ * are answered before they get here — the track menu greys its copy actions, and
14
+ * `addTrackFromWidget` opens the track inline instead of adding it — so what
15
+ * survives to report is a caller that had `inlineConf` available and did not
16
+ * take it. That is the reading the message is written for.
17
+ *
18
+ * `console.error` and never `throw`, matching `assertDisplayContract`: these
19
+ * writes happen inside launchers and menu handlers where an exception is caught
20
+ * and reported as a failed track, which would hide the violation. The jest gate
21
+ * (`config/jest/contractGate.js`) is what listens in tree; out of tree
22
+ * `contractReports` does, once a host arms it.
23
+ */
24
+ export declare function assertTrackConfOutlivesItsAssemblies(session: unknown, trackConf: AnyConfigurationModel | Record<string, unknown>, destination: string): void;
@@ -0,0 +1,37 @@
1
+ import { namesTemporaryAssembly } from '@jbrowse/core/util';
2
+ import { reportContractViolation } from '@jbrowse/render-core/contractReports';
3
+ /**
4
+ * Checks that a track config being written into a session or config list is one
5
+ * something can still draw tomorrow.
6
+ *
7
+ * A config naming nothing but a temporary assembly is dead the moment its view
8
+ * closes, and no list outside that view has anyone to sweep it — which is the
9
+ * whole of ADR-084. A track only one view can draw carries its config on the
10
+ * track instead (`showTrackGeneric`'s `inlineConf`), so reaching a session list
11
+ * with one is the mistake, not the cleanup afterwards.
12
+ *
13
+ * **This reports a write, not a click.** The two flows a user can drive into it
14
+ * are answered before they get here — the track menu greys its copy actions, and
15
+ * `addTrackFromWidget` opens the track inline instead of adding it — so what
16
+ * survives to report is a caller that had `inlineConf` available and did not
17
+ * take it. That is the reading the message is written for.
18
+ *
19
+ * `console.error` and never `throw`, matching `assertDisplayContract`: these
20
+ * writes happen inside launchers and menu handlers where an exception is caught
21
+ * and reported as a failed track, which would hide the violation. The jest gate
22
+ * (`config/jest/contractGate.js`) is what listens in tree; out of tree
23
+ * `contractReports` does, once a host arms it.
24
+ */
25
+ export function assertTrackConfOutlivesItsAssemblies(session, trackConf, destination) {
26
+ if (!namesTemporaryAssembly(session, trackConf)) {
27
+ return;
28
+ }
29
+ const { trackId } = trackConf;
30
+ reportContractViolation('session', `${destination} was given "${trackId}", ` +
31
+ `which names a temporary assembly — one a comparative view synthesized ` +
32
+ `and gives back when it closes, so this config outlives the only ` +
33
+ `assembly that could draw it and nothing sweeps the list it landed in. ` +
34
+ `A track only one view can draw passes its config to that view's ` +
35
+ `showTrack as \`inlineConf\` instead, which puts it on the track and ` +
36
+ `takes it out with the view. See ADR-084.`);
37
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Compile-time assertion that `Actual` is assignable to `Expected`, evaluating
3
+ * to `Actual`. There is no runtime component — referencing the alias forces the
4
+ * check, and a mismatch surfaces as a type error at the reference site.
5
+ *
6
+ * The main use is binding a hand-written "shadow" contract to the real type it
7
+ * shadows so the two can't silently drift. MST's `getParent<T>(self)` is an
8
+ * unchecked assertion (`T` has no structural link to the actual parent), so a
9
+ * child model that reaches into its parent via a local shape can keep compiling
10
+ * after the real parent renames or drops a member — until it throws at runtime.
11
+ * Pinning `AssertExtends<RealRootModel, ShadowParent>` turns that into a build
12
+ * error:
13
+ *
14
+ * ```ts
15
+ * interface SessionModelParent {
16
+ * version: string
17
+ * assemblyManager: AssemblyManager
18
+ * }
19
+ * // real root must satisfy the shadow the session reaches for
20
+ * export type _Check = AssertExtends<ViewModel, SessionModelParent>
21
+ * ```
22
+ */
23
+ export type AssertExtends<Actual extends Expected, Expected> = Actual;
24
+ /**
25
+ * Compile-time assertion that a type has not degraded to `any`. Pass it the
26
+ * `IsAny` of the type you are pinning; a failure reads `Type 'true' does not
27
+ * satisfy the constraint 'false'`.
28
+ *
29
+ * ```ts
30
+ * export type _ViewIsTyped = AssertNotAny<IsAny<ViewModel['session']['view']>>
31
+ * ```
32
+ *
33
+ * This is the one check the others cannot make for you. `any` satisfies every
34
+ * `AssertExtends`, implements every `SessionWith*` contract, and passes tsc,
35
+ * jest and lint — so a surface that silently becomes `any` switches off type
36
+ * checking everywhere downstream while every gate stays green.
37
+ *
38
+ * Composed MST models are where it happens here. `types.compose`'s overloads
39
+ * are declared over `IModelType<P, O, FC, FS>`, so a model passed in as a naked
40
+ * type parameter has nothing to infer those four from and the composed result
41
+ * degrades.
42
+ *
43
+ * Naming `any` twice at the call site is not an oversight. It takes the computed
44
+ * boolean because the constraint has to be checked where it is concrete, and all
45
+ * three single-argument spellings were tried and fail:
46
+ *
47
+ * - `AssertNotAny<T> = AssertExtends<IsAny<T>, false>` — unbound, `IsAny<T>` is
48
+ * `boolean`, so the alias fails at its own declaration.
49
+ * - `AssertNotAny<T, _ extends false = IsAny<T>> = T` — the tempting one. Same
50
+ * declaration-site failure, and it does not even catch: instantiated at `any`
51
+ * it reports nothing.
52
+ * - `AssertNotAny<T extends IsAny<T> extends true ? never : unknown>` — TS2313,
53
+ * circular constraint.
54
+ */
55
+ export type AssertNotAny<IsItAny extends false> = IsItAny;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import type { BlobLocation } from '@jbrowse/core/util';
2
+ import type { LooseTrackInput } from '@jbrowse/core/util/tracks';
3
+ import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
4
+ export type TrackConf = Record<string, unknown>;
5
+ /**
6
+ * What a controller accepts per track: a full config, a bare data-file URL, or
7
+ * `{ uri, index?, ...extra }`. The loose forms expand through core's
8
+ * `guessTrackConf`, the same inference the "Add track" flow runs.
9
+ */
10
+ export type TrackInput = string | TrackConf;
11
+ interface ControllerView {
12
+ tracks: {
13
+ configuration: {
14
+ trackId: string;
15
+ };
16
+ }[];
17
+ launchTrack: (trackId: string) => Promise<unknown>;
18
+ hideTrack: (trackId: string) => unknown;
19
+ }
20
+ export interface ControllerSession extends IStateTreeNode {
21
+ view: ControllerView;
22
+ getTrackById: (trackId: string) => unknown;
23
+ addSessionTrackConf: (conf: TrackConf) => unknown;
24
+ }
25
+ export declare function isLooseTrack(track: TrackInput): track is string | LooseTrackInput;
26
+ /**
27
+ * Stamp the view's assembly onto a full-config track that omits
28
+ * `assemblyNames`. A single-view embed has exactly one assembly, so it is
29
+ * unambiguous — and doing it here frees every host (R htmlwidgets, anywidget,
30
+ * vanilla JS) from computing the name itself, which it often cannot: the
31
+ * assembly may have arrived as a hub name only the view resolved. Loose tracks
32
+ * take the same name through `guessTrackConf`'s `assemblyName` argument.
33
+ */
34
+ export declare function withAssemblyName(track: TrackConf, assemblyName?: string): TrackConf;
35
+ /**
36
+ * Expand every loose entry into a full track config; stamp the assembly name
37
+ * onto the ones already written out. `node` is any node of the live tree — the
38
+ * pluginManager whose format plugins drive the guess comes off its env.
39
+ */
40
+ export declare function resolveTracks(tracks: TrackInput[], node: IStateTreeNode, assemblyName?: string, localFiles?: Record<string, BlobLocation>): TrackConf[];
41
+ /**
42
+ * Register each track config only if the session cannot already resolve it, then
43
+ * show it. `getTrackById` resolves catalog + connection + session tracks, so a
44
+ * config already seeded into the catalog is left alone rather than shadowed by a
45
+ * duplicate in `sessionTracks` — which is also what `addSessionTrackConf`
46
+ * dedupes on, so this states the intent rather than supplying it.
47
+ *
48
+ * Session-scoped: these are the host's declared tracks for one embed, not a
49
+ * catalog an admin curates.
50
+ */
51
+ export declare function openTracks(session: ControllerSession, tracks: TrackConf[]): Promise<void>;
52
+ /** Open every wanted track and close any others the view currently shows. */
53
+ export declare function reconcileTracks(session: ControllerSession, tracks: TrackConf[]): Promise<void>;
54
+ /** Merge a hub's own search adapters with the ones the caller supplied. */
55
+ export declare function mergeSearchAdapters<T>(a: readonly T[] | undefined, b: readonly T[] | undefined): T[] | undefined;
56
+ export {};
@@ -0,0 +1,90 @@
1
+ import { getEnv } from '@jbrowse/core/util';
2
+ import { guessTrackConf } from '@jbrowse/core/util/tracks';
3
+ import { isAlive } from '@jbrowse/mobx-state-tree';
4
+ import { resolveLocalFileUris } from './localFiles.js';
5
+ export function isLooseTrack(track) {
6
+ return typeof track === 'string' || (!('adapter' in track) && 'uri' in track);
7
+ }
8
+ /**
9
+ * Stamp the view's assembly onto a full-config track that omits
10
+ * `assemblyNames`. A single-view embed has exactly one assembly, so it is
11
+ * unambiguous — and doing it here frees every host (R htmlwidgets, anywidget,
12
+ * vanilla JS) from computing the name itself, which it often cannot: the
13
+ * assembly may have arrived as a hub name only the view resolved. Loose tracks
14
+ * take the same name through `guessTrackConf`'s `assemblyName` argument.
15
+ */
16
+ export function withAssemblyName(track, assemblyName) {
17
+ return assemblyName !== undefined && !('assemblyNames' in track)
18
+ ? { ...track, assemblyNames: [assemblyName] }
19
+ : track;
20
+ }
21
+ /**
22
+ * Expand every loose entry into a full track config; stamp the assembly name
23
+ * onto the ones already written out. `node` is any node of the live tree — the
24
+ * pluginManager whose format plugins drive the guess comes off its env.
25
+ */
26
+ export function resolveTracks(tracks, node, assemblyName, localFiles) {
27
+ const { pluginManager } = getEnv(node);
28
+ return tracks.map(track => {
29
+ const conf = isLooseTrack(track)
30
+ ? guessTrackConf(track, pluginManager, assemblyName)
31
+ : // full configs are stamped here too, not just in the build() catalog
32
+ // seed: a config that first appears through addTrack never passes
33
+ // through that seed, and would otherwise reach addSessionTrackConf with no
34
+ // assemblyNames and silently fail to display
35
+ withAssemblyName(track, assemblyName);
36
+ // after the guess, so the adapter has already derived its index sibling
37
+ // from the uri string and both get swapped for blobs together
38
+ return localFiles ? resolveLocalFileUris(conf, localFiles) : conf;
39
+ });
40
+ }
41
+ /**
42
+ * Register each track config only if the session cannot already resolve it, then
43
+ * show it. `getTrackById` resolves catalog + connection + session tracks, so a
44
+ * config already seeded into the catalog is left alone rather than shadowed by a
45
+ * duplicate in `sessionTracks` — which is also what `addSessionTrackConf`
46
+ * dedupes on, so this states the intent rather than supplying it.
47
+ *
48
+ * Session-scoped: these are the host's declared tracks for one embed, not a
49
+ * catalog an admin curates.
50
+ */
51
+ export async function openTracks(session, tracks) {
52
+ // sequential, not Promise.all: each display's state model may be a separate
53
+ // dynamic import, and racing them would land the tracks on the view in
54
+ // whatever order the chunks happen to resolve rather than the caller's
55
+ for (const conf of tracks) {
56
+ // the engine can be destroyed while a chunk is in flight (an unmounted
57
+ // embed); reading the dead tree from this floating promise would throw
58
+ // uncatchably
59
+ if (!isAlive(session.view)) {
60
+ return;
61
+ }
62
+ const trackId = conf.trackId;
63
+ if (!session.getTrackById(trackId)) {
64
+ session.addSessionTrackConf(conf);
65
+ }
66
+ await session.view.launchTrack(trackId);
67
+ }
68
+ }
69
+ /** Open every wanted track and close any others the view currently shows. */
70
+ export async function reconcileTracks(session, tracks) {
71
+ const { view } = session;
72
+ const wanted = new Set(tracks.map(t => t.trackId));
73
+ await openTracks(session, tracks);
74
+ if (!isAlive(view)) {
75
+ return;
76
+ }
77
+ // materialize the ids first: hideTrack splices view.tracks, so iterating it
78
+ // live would skip entries
79
+ const unwanted = view.tracks
80
+ .map(track => track.configuration.trackId)
81
+ .filter(trackId => !wanted.has(trackId));
82
+ for (const trackId of unwanted) {
83
+ view.hideTrack(trackId);
84
+ }
85
+ }
86
+ /** Merge a hub's own search adapters with the ones the caller supplied. */
87
+ export function mergeSearchAdapters(a, b) {
88
+ const merged = [...(a ?? []), ...(b ?? [])];
89
+ return merged.length ? merged : undefined;
90
+ }
@@ -0,0 +1,21 @@
1
+ import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
2
+ export interface EmbeddedRoot extends IStateTreeNode {
3
+ session?: unknown;
4
+ rpcManager: {
5
+ destroy: () => void;
6
+ };
7
+ }
8
+ /**
9
+ * Tear down an engine built by an embedded product's `createViewState`:
10
+ * terminate its RPC worker threads and destroy the MST tree so its
11
+ * `addDisposer`'d autoruns stop.
12
+ *
13
+ * Unmounting the React tree is not enough — the engine is not owned by React.
14
+ * A host that builds and discards engines (a Jupyter cell re-run, an SPA route
15
+ * change, a controller's `destroy()`) otherwise orphans a whole worker pool and
16
+ * a live autorun set per discarded engine. A component that owns one engine for
17
+ * the lifetime of the page never needs this.
18
+ *
19
+ * Idempotent: destroying an already-destroyed engine is a no-op.
20
+ */
21
+ export declare function destroyViewState(viewState: EmbeddedRoot): void;
@@ -0,0 +1,29 @@
1
+ import { destroy, isAlive } from '@jbrowse/mobx-state-tree';
2
+ /**
3
+ * Tear down an engine built by an embedded product's `createViewState`:
4
+ * terminate its RPC worker threads and destroy the MST tree so its
5
+ * `addDisposer`'d autoruns stop.
6
+ *
7
+ * Unmounting the React tree is not enough — the engine is not owned by React.
8
+ * A host that builds and discards engines (a Jupyter cell re-run, an SPA route
9
+ * change, a controller's `destroy()`) otherwise orphans a whole worker pool and
10
+ * a live autorun set per discarded engine. A component that owns one engine for
11
+ * the lifetime of the page never needs this.
12
+ *
13
+ * Idempotent: destroying an already-destroyed engine is a no-op.
14
+ */
15
+ export function destroyViewState(viewState) {
16
+ if (isAlive(viewState)) {
17
+ // Materialize the session before killing the tree. MST snapshots each dying
18
+ // node on the way down, and snapshotting a node that was never observed
19
+ // instantiates it right then — running its afterAttach during death
20
+ // finalization, where creating a further observable (the config it reads)
21
+ // throws "the creation of the observable instance must be done on the
22
+ // initializing phase". Reachable whenever an engine is destroyed before it
23
+ // ever rendered, which is exactly what React StrictMode does to the
24
+ // build-in-a-ref-callback pattern: mount, clean up, mount again.
25
+ void viewState.session;
26
+ viewState.rpcManager.destroy();
27
+ destroy(viewState);
28
+ }
29
+ }
package/esm/index.d.ts CHANGED
@@ -1,4 +1,31 @@
1
- export * from './RootModel/index.ts';
2
- export * from './Session/index.ts';
3
- export * from './ui/index.ts';
4
- export * from './rpcWorker.ts';
1
+ export type { AssertExtends, AssertNotAny } from './assertExtends.ts';
2
+ export { asRoot, asSession } from './siblingCast.ts';
3
+ export { scheduleDetachedDestroy } from './scheduleDetachedDestroy.ts';
4
+ export { BaseRootModelFactory, FormatAboutConfigSchemaFactory, FormatDetailsConfigSchemaFactory, HierarchicalConfigSchemaFactory, InternetAccountsRootModelMixin, PreferencesConfigSchemaFactory, createConfigModel, exportSessionMenuItem, importSessionMenuItem, isRootModel, newSessionMenuItem, openConnectionMenuItem, openTrackMenuItem, pluginStoreMenuItem, preferencesMenuItem, redoMenuItem, undoMenuItem, workspacesMenuItem, } from './RootModel/index.ts';
5
+ export type { BaseRootModel, BaseRootModelType, RootConfigurationSnapshot, RootModelWithInternetAccounts, RootModelWithInternetAccountsType, } from './RootModel/index.ts';
6
+ export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.ts';
7
+ export type { AssertSessionModel, BaseSession, BaseSessionType, ConnectionTrackConfigEntry, EditableTrackConfig, PlainTrackConfig, ReferringNode, SessionWithConnections, SessionWithConnectionsType, SessionWithDrawerWidgets, SessionWithDrawerWidgetsType, SessionWithMultipleViews, SessionWithMultipleViewsType, SessionWithReferenceManagement, SessionWithReferenceManagementType, SessionWithSessionTracks, SessionWithSessionTracksType, SessionWithThemes, SessionWithThemesType, SessionWithTracks, SessionWithTracksType, } from './Session/index.ts';
8
+ export { AboutDialog, Drawer, DrawerControls, DrawerHeader, DrawerWidget, DrawerWidgetSelector, ModalWidget, ModalWidgetAppBar, PreferencesDialog, WidgetBody, WidgetHeading, drawerGridTemplateColumns, } from './ui/index.ts';
9
+ export type { PreferencesDialogSession, PreferencesPanelDescriptor, } from './ui/index.ts';
10
+ export { initializeWorker } from './rpcWorker.ts';
11
+ export { toPluginLoadRecord } from './pluginInput.ts';
12
+ export type { PluginInput } from './pluginInput.ts';
13
+ export { loadRuntimePlugins } from './loadPlugins.ts';
14
+ export type { LoadPluginsArgs } from './loadPlugins.ts';
15
+ export { decodeSessionFromUrl, encodeSessionToUrl } from './sessionUrl.ts';
16
+ export type { SessionSnapshot } from './sessionUrl.ts';
17
+ export { mergeLocalFiles, normalizeAdapterSnapshots, registerLocalFiles, resolveLocalFileUris, } from './localFiles.ts';
18
+ export type { LocalFileInput } from './localFiles.ts';
19
+ export { isLooseTrack, mergeSearchAdapters, openTracks, reconcileTracks, resolveTracks, withAssemblyName, } from './controllerTracks.ts';
20
+ export type { ControllerSession, TrackConf, TrackInput, } from './controllerTracks.ts';
21
+ export { resolveAssemblies, resolveAssembly } from './resolveAssemblies.ts';
22
+ export type { AssemblyInput, ResolvedAssemblies } from './resolveAssemblies.ts';
23
+ export { destroyViewState } from './destroyViewState.ts';
24
+ export { useAsyncEngineLifecycle, useCreateOnce, useCreateOnceAsync, useDestroyOnUnmount, } from './useEngineLifecycle.ts';
25
+ export { getSessionSnapshot, observeSession } from './observeSession.ts';
26
+ export type { SessionObservers, ViewLocation } from './observeSession.ts';
27
+ export { MIGRATED_DISPLAY_INSTANCE_KEYS, migrateConfigSnapshot, migrateSessionSnapshot, } from './sessionMigrations/index.ts';
28
+ export { DEFAULT_WEB_BASE_URL, analyzeWebPortability, buildWebExportUrl, filterSessionInPlace, planWebExport, } from './sessionUtils.ts';
29
+ export type { AssemblySnapshot, DroppedSessionNode, HostedBaseConfig, NonPortableLocation, SelfContainedReason, TrackSnapshot, WebExportInput, WebExportPlan, WebPortabilityReport, } from './sessionUtils.ts';
30
+ export { describeUnbuildableNodes, pruneUnbuildableNodes, } from './pruneUnbuildableNodes.ts';
31
+ export type { HeldNode, UnbuildableNode } from './pruneUnbuildableNodes.ts';
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 './scheduleDetachedDestroy.js';
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, bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.js';
5
+ export { AboutDialog, 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 { mergeLocalFiles, normalizeAdapterSnapshots, registerLocalFiles, resolveLocalFileUris, } from './localFiles.js';
14
+ // the track half of an imperative controller: loose track specs -> configs, and
15
+ // opening exactly the wanted set
16
+ export { isLooseTrack, mergeSearchAdapters, openTracks, reconcileTracks, resolveTracks, withAssemblyName, } from './controllerTracks.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 { MIGRATED_DISPLAY_INSTANCE_KEYS, migrateConfigSnapshot, migrateSessionSnapshot, } 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,33 @@
1
+ import type { LoadedPlugin } 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
+ export declare function loadRuntimePlugins(pluginDefinitions: PluginDefinition[], { dropVendored, jbrowseVersion, ...args }: LoadPluginsArgs & {
31
+ dropVendored: boolean;
32
+ jbrowseVersion: string;
33
+ }): Promise<never[] | import("@jbrowse/core/PluginLoader").PluginRecord[]>;