@genome-spy/core 0.63.0 → 0.65.0

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 (138) hide show
  1. package/dist/bundle/{index-CCJIjehY.js → AbortablePromiseCache-CcuMrnn7.js} +22 -91
  2. package/dist/bundle/browser-txUcLy2H.js +123 -0
  3. package/dist/bundle/index-BQpbYrv4.js +1712 -0
  4. package/dist/bundle/index-BhtHKLUo.js +73 -0
  5. package/dist/bundle/index-C0llXMqm.js +280 -0
  6. package/dist/bundle/index-CCe8rnZz.js +716 -0
  7. package/dist/bundle/index-CD7FLu9x.js +269 -0
  8. package/dist/bundle/{index-C08YCM2T.js → index-D-w7Mmt9.js} +246 -126
  9. package/dist/bundle/index-D74H8TTz.js +508 -0
  10. package/dist/bundle/index-DhcU-Gk-.js +1487 -0
  11. package/dist/bundle/index.es.js +5394 -4989
  12. package/dist/bundle/index.js +420 -362
  13. package/dist/bundle/inflate-DRgHi_KK.js +1050 -0
  14. package/dist/schema.json +93 -13
  15. package/dist/src/data/collector.d.ts +7 -2
  16. package/dist/src/data/collector.d.ts.map +1 -1
  17. package/dist/src/data/collector.js +13 -2
  18. package/dist/src/data/dataFlow.d.ts +20 -42
  19. package/dist/src/data/dataFlow.d.ts.map +1 -1
  20. package/dist/src/data/dataFlow.js +57 -80
  21. package/dist/src/data/dataFlow.test.js +35 -2
  22. package/dist/src/data/flowHandle.d.ts +15 -0
  23. package/dist/src/data/flowHandle.d.ts.map +1 -0
  24. package/dist/src/data/flowHandle.js +13 -0
  25. package/dist/src/data/flowInit.d.ts +85 -0
  26. package/dist/src/data/flowInit.d.ts.map +1 -0
  27. package/dist/src/data/flowInit.js +238 -0
  28. package/dist/src/data/flowInit.test.d.ts +2 -0
  29. package/dist/src/data/flowInit.test.d.ts.map +1 -0
  30. package/dist/src/data/flowInit.test.js +413 -0
  31. package/dist/src/data/flowOptimizer.d.ts +6 -4
  32. package/dist/src/data/flowOptimizer.d.ts.map +1 -1
  33. package/dist/src/data/flowOptimizer.js +29 -14
  34. package/dist/src/data/flowOptimizer.test.js +20 -15
  35. package/dist/src/data/sources/lazy/bamSource.js +1 -1
  36. package/dist/src/data/sources/lazy/bigBedSource.js +1 -1
  37. package/dist/src/data/sources/lazy/bigWigSource.js +1 -1
  38. package/dist/src/data/sources/lazy/gff3Source.d.ts +2 -6
  39. package/dist/src/data/sources/lazy/gff3Source.d.ts.map +1 -1
  40. package/dist/src/data/sources/lazy/gff3Source.js +4 -8
  41. package/dist/src/data/sources/lazy/indexedFastaSource.d.ts.map +1 -1
  42. package/dist/src/data/sources/lazy/indexedFastaSource.js +17 -17
  43. package/dist/src/data/sources/lazy/tabixSource.js +1 -1
  44. package/dist/src/genomeSpy.d.ts +1 -1
  45. package/dist/src/genomeSpy.d.ts.map +1 -1
  46. package/dist/src/genomeSpy.js +18 -61
  47. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  48. package/dist/src/gl/webGLHelper.js +8 -0
  49. package/dist/src/marks/link.fragment.glsl.js +1 -1
  50. package/dist/src/marks/link.vertex.glsl.js +1 -1
  51. package/dist/src/marks/mark.d.ts +1 -0
  52. package/dist/src/marks/mark.d.ts.map +1 -1
  53. package/dist/src/marks/mark.js +22 -1
  54. package/dist/src/marks/point.fragment.glsl.js +1 -1
  55. package/dist/src/marks/point.vertex.glsl.js +1 -1
  56. package/dist/src/marks/rect.fragment.glsl.js +1 -1
  57. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  58. package/dist/src/marks/rule.fragment.glsl.js +1 -1
  59. package/dist/src/marks/rule.vertex.glsl.js +1 -1
  60. package/dist/src/marks/text.fragment.glsl.js +1 -1
  61. package/dist/src/marks/text.vertex.glsl.js +1 -1
  62. package/dist/src/selection/selection.d.ts +5 -0
  63. package/dist/src/selection/selection.d.ts.map +1 -1
  64. package/dist/src/selection/selection.js +43 -6
  65. package/dist/src/selection/selection.test.d.ts +2 -0
  66. package/dist/src/selection/selection.test.d.ts.map +1 -0
  67. package/dist/src/selection/selection.test.js +14 -0
  68. package/dist/src/spec/parameter.d.ts +28 -2
  69. package/dist/src/spec/sampleView.d.ts +3 -2
  70. package/dist/src/styles/{genome-spy.scss → genome-spy.css} +25 -21
  71. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  72. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  73. package/dist/src/styles/genome-spy.css.js +264 -195
  74. package/dist/src/styles/update.sh +14 -4
  75. package/dist/src/types/viewContext.d.ts +1 -1
  76. package/dist/src/utils/expression.d.ts +5 -0
  77. package/dist/src/utils/expression.d.ts.map +1 -1
  78. package/dist/src/utils/expression.js +37 -0
  79. package/dist/src/utils/interactionEvent.d.ts +18 -1
  80. package/dist/src/utils/interactionEvent.d.ts.map +1 -1
  81. package/dist/src/utils/interactionEvent.js +101 -1
  82. package/dist/src/utils/interactionEvent.test.d.ts +2 -0
  83. package/dist/src/utils/interactionEvent.test.d.ts.map +1 -0
  84. package/dist/src/utils/interactionEvent.test.js +35 -0
  85. package/dist/src/view/axisResolution.d.ts +5 -0
  86. package/dist/src/view/axisResolution.d.ts.map +1 -1
  87. package/dist/src/view/axisResolution.js +16 -1
  88. package/dist/src/view/facetView.d.ts +1 -1
  89. package/dist/src/view/facetView.d.ts.map +1 -1
  90. package/dist/src/view/facetView.js +1 -0
  91. package/dist/src/view/flowBuilder.d.ts +2 -2
  92. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  93. package/dist/src/view/flowBuilder.js +21 -4
  94. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  95. package/dist/src/view/gridView/gridView.js +13 -0
  96. package/dist/src/view/gridView/selectionRect.d.ts +8 -4
  97. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  98. package/dist/src/view/gridView/selectionRect.js +28 -3
  99. package/dist/src/view/gridView/selectionRect.test.d.ts +2 -0
  100. package/dist/src/view/gridView/selectionRect.test.d.ts.map +1 -0
  101. package/dist/src/view/gridView/selectionRect.test.js +87 -0
  102. package/dist/src/view/paramMediator.d.ts +2 -1
  103. package/dist/src/view/paramMediator.d.ts.map +1 -1
  104. package/dist/src/view/paramMediator.js +13 -1
  105. package/dist/src/view/paramMediator.test.js +22 -0
  106. package/dist/src/view/scaleResolution.d.ts +5 -0
  107. package/dist/src/view/scaleResolution.d.ts.map +1 -1
  108. package/dist/src/view/scaleResolution.js +10 -0
  109. package/dist/src/view/testUtils.d.ts.map +1 -1
  110. package/dist/src/view/testUtils.js +16 -4
  111. package/dist/src/view/unitView.d.ts.map +1 -1
  112. package/dist/src/view/unitView.js +103 -10
  113. package/dist/src/view/view.d.ts +17 -1
  114. package/dist/src/view/view.d.ts.map +1 -1
  115. package/dist/src/view/view.js +57 -1
  116. package/dist/src/view/viewDispose.test.d.ts +2 -0
  117. package/dist/src/view/viewDispose.test.d.ts.map +1 -0
  118. package/dist/src/view/viewDispose.test.js +110 -0
  119. package/dist/src/view/viewUtils.d.ts +4 -4
  120. package/dist/src/view/viewUtils.d.ts.map +1 -1
  121. package/dist/src/view/viewUtils.js +19 -15
  122. package/dist/src/view/viewUtils.test.d.ts +2 -0
  123. package/dist/src/view/viewUtils.test.d.ts.map +1 -0
  124. package/dist/src/view/viewUtils.test.js +87 -0
  125. package/package.json +16 -16
  126. package/dist/bundle/__vite-browser-external-C--ziKoh.js +0 -8
  127. package/dist/bundle/_commonjsHelpers-DjF3Plf2.js +0 -26
  128. package/dist/bundle/index-5ajWdKly.js +0 -1319
  129. package/dist/bundle/index-B03-Om4z.js +0 -274
  130. package/dist/bundle/index-BftNdA0O.js +0 -27
  131. package/dist/bundle/index-Bg7C4Xat.js +0 -2750
  132. package/dist/bundle/index-C3QR8Lv6.js +0 -2131
  133. package/dist/bundle/index-DTcHjAHp.js +0 -505
  134. package/dist/bundle/index-DnIkxb0L.js +0 -1025
  135. package/dist/bundle/index-Ww3TAo6_.js +0 -71
  136. package/dist/bundle/index-g8iXgW0W.js +0 -651
  137. package/dist/bundle/long-B-FASCSo.js +0 -2387
  138. package/dist/bundle/remoteFile-BuaqFGWk.js +0 -94
@@ -0,0 +1,413 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+
3
+ import { createTestViewContext } from "../view/testUtils.js";
4
+ import { buildDataFlow } from "../view/flowBuilder.js";
5
+ import { optimizeDataFlow } from "./flowOptimizer.js";
6
+ import {
7
+ collectNearestViewSubtreeDataSources,
8
+ collectViewSubtreeDataSources,
9
+ initializeViewSubtree,
10
+ loadViewSubtreeData,
11
+ syncFlowHandles,
12
+ } from "./flowInit.js";
13
+
14
+ describe("flowInit", () => {
15
+ test("syncs handles to canonical data sources after merge", async () => {
16
+ const context = createTestViewContext();
17
+ context.getNamedDataFromProvider = () => [];
18
+ context.addBroadcastListener = () => undefined;
19
+ context.removeBroadcastListener = () => undefined;
20
+
21
+ /** @type {import("../spec/view.js").HConcatSpec} */
22
+ const spec = {
23
+ hconcat: [
24
+ {
25
+ data: { name: "shared" },
26
+ mark: "point",
27
+ encoding: {
28
+ x: { field: "x", type: "quantitative" },
29
+ },
30
+ },
31
+ {
32
+ data: { name: "shared" },
33
+ mark: "point",
34
+ encoding: {
35
+ x: { field: "x", type: "quantitative" },
36
+ },
37
+ },
38
+ ],
39
+ };
40
+
41
+ const root = await context.createOrImportView(spec, null, null, "root");
42
+
43
+ const flow = buildDataFlow(root, context.dataFlow);
44
+ const canonicalBySource = optimizeDataFlow(flow);
45
+ syncFlowHandles(root, canonicalBySource);
46
+
47
+ const concatRoot =
48
+ /** @type {import("../view/concatView.js").default} */ (root);
49
+ const left = concatRoot.children[0];
50
+ const right = concatRoot.children[1];
51
+
52
+ expect(left.flowHandle.dataSource).toBeDefined();
53
+ expect(right.flowHandle.dataSource).toBeDefined();
54
+ expect(left.flowHandle.dataSource).toBe(right.flowHandle.dataSource);
55
+
56
+ const sharedSources = flow.dataSources.filter(
57
+ (/** @type {import("./sources/dataSource.js").default} */ source) =>
58
+ source.identifier === "shared"
59
+ );
60
+ expect(sharedSources).toEqual([left.flowHandle.dataSource]);
61
+ });
62
+
63
+ test("initializeViewSubtree wires collector updates for subtree loads", async () => {
64
+ const context = createTestViewContext();
65
+ context.getNamedDataFromProvider = () => [];
66
+ context.addBroadcastListener = () => undefined;
67
+ context.removeBroadcastListener = () => undefined;
68
+
69
+ /** @type {import("../spec/view.js").UnitSpec} */
70
+ const spec = {
71
+ data: { values: [{ x: 1 }, { x: 2 }] },
72
+ mark: "point",
73
+ encoding: {
74
+ x: { field: "x", type: "quantitative" },
75
+ },
76
+ };
77
+
78
+ const root = await context.createOrImportView(spec, null, null, "root");
79
+ const { dataSources } = initializeViewSubtree(root, context.dataFlow);
80
+
81
+ // This guards subtree-only initialization: dynamic view rebuilds should still
82
+ // trigger mark updates when their local collectors complete.
83
+ const unitView = /** @type {import("../view/unitView.js").default} */ (
84
+ root
85
+ );
86
+ const initializeSpy = vi.spyOn(unitView.mark, "initializeData");
87
+
88
+ await Promise.all(
89
+ Array.from(dataSources).map((dataSource) => dataSource.load())
90
+ );
91
+
92
+ expect(initializeSpy).toHaveBeenCalledTimes(1);
93
+ initializeSpy.mockRestore();
94
+ });
95
+
96
+ test("disposeSubtree removes observers before rebuilding subtree", async () => {
97
+ const context = createTestViewContext();
98
+ context.getNamedDataFromProvider = () => [{ x: 1 }];
99
+ context.addBroadcastListener = () => undefined;
100
+ context.removeBroadcastListener = () => undefined;
101
+
102
+ /** @type {import("../spec/view.js").UnitSpec} */
103
+ const spec = {
104
+ data: { name: "shared" },
105
+ mark: "point",
106
+ encoding: {
107
+ x: { field: "x", type: "quantitative" },
108
+ },
109
+ };
110
+
111
+ const firstRoot = await context.createOrImportView(
112
+ spec,
113
+ null,
114
+ null,
115
+ "first"
116
+ );
117
+ const { dataSources: firstSources } = initializeViewSubtree(
118
+ firstRoot,
119
+ context.dataFlow
120
+ );
121
+
122
+ const firstUnit = /** @type {import("../view/unitView.js").default} */ (
123
+ firstRoot
124
+ );
125
+ const firstCollector = firstUnit.flowHandle.collector;
126
+ const firstInitializeSpy = vi.spyOn(firstUnit.mark, "initializeData");
127
+
128
+ await Promise.all(
129
+ Array.from(firstSources).map((dataSource) => dataSource.load())
130
+ );
131
+
132
+ expect(firstInitializeSpy).toHaveBeenCalledTimes(1);
133
+ firstInitializeSpy.mockRestore();
134
+
135
+ firstRoot.disposeSubtree();
136
+
137
+ // This prevents stale observers from firing after a subtree is rebuilt.
138
+ expect(firstCollector.observers.size).toBe(0);
139
+
140
+ const secondRoot = await context.createOrImportView(
141
+ spec,
142
+ null,
143
+ null,
144
+ "second"
145
+ );
146
+ const { dataSources: secondSources } = initializeViewSubtree(
147
+ secondRoot,
148
+ context.dataFlow
149
+ );
150
+
151
+ const secondUnit =
152
+ /** @type {import("../view/unitView.js").default} */ (secondRoot);
153
+ const secondInitializeSpy = vi.spyOn(secondUnit.mark, "initializeData");
154
+
155
+ await Promise.all(
156
+ Array.from(secondSources).map((dataSource) => dataSource.load())
157
+ );
158
+
159
+ expect(secondInitializeSpy).toHaveBeenCalledTimes(1);
160
+ secondInitializeSpy.mockRestore();
161
+ });
162
+
163
+ test("disposeSubtree prunes named source branches", async () => {
164
+ const context = createTestViewContext();
165
+ context.getNamedDataFromProvider = () => [{ x: 1 }];
166
+ context.addBroadcastListener = () => undefined;
167
+ context.removeBroadcastListener = () => undefined;
168
+
169
+ /** @type {import("../spec/view.js").UnitSpec} */
170
+ const spec = {
171
+ data: { name: "shared" },
172
+ mark: "point",
173
+ encoding: {
174
+ x: { field: "x", type: "quantitative" },
175
+ },
176
+ };
177
+
178
+ const root = await context.createOrImportView(spec, null, null, "root");
179
+ initializeViewSubtree(root, context.dataFlow);
180
+
181
+ const unitView = /** @type {import("../view/unitView.js").default} */ (
182
+ root
183
+ );
184
+ const dataSource = unitView.flowHandle.dataSource;
185
+
186
+ // This guards against stale flow branches when a subtree is disposed.
187
+ expect(context.dataFlow.dataSources).toContain(dataSource);
188
+ expect(dataSource.children.length).toBeGreaterThan(0);
189
+
190
+ root.disposeSubtree();
191
+
192
+ expect(dataSource.children.length).toBe(0);
193
+ expect(context.dataFlow.dataSources).not.toContain(dataSource);
194
+ });
195
+
196
+ test("collectNearestViewSubtreeDataSources stops at nested sources", async () => {
197
+ const context = createTestViewContext();
198
+ context.addBroadcastListener = () => undefined;
199
+ context.removeBroadcastListener = () => undefined;
200
+
201
+ /** @type {import("../spec/view.js").LayerSpec} */
202
+ const spec = {
203
+ data: { values: [{ x: 0 }] },
204
+ layer: [
205
+ {
206
+ data: { values: [{ x: 1 }] },
207
+ mark: "point",
208
+ encoding: {
209
+ x: { field: "x", type: "quantitative" },
210
+ },
211
+ },
212
+ {
213
+ mark: "point",
214
+ encoding: {
215
+ x: { field: "x", type: "quantitative" },
216
+ },
217
+ },
218
+ ],
219
+ };
220
+
221
+ const root = await context.createOrImportView(spec, null, null, "root");
222
+ initializeViewSubtree(root, context.dataFlow);
223
+
224
+ // Nearest-source semantics: a top-level source hides deeper sources.
225
+ const sources = collectNearestViewSubtreeDataSources(root);
226
+ expect(sources.size).toBe(1);
227
+
228
+ const [rootSource] = Array.from(sources);
229
+ const layerRoot =
230
+ /** @type {import("../view/layerView.js").default} */ (root);
231
+ const childWithSource = layerRoot.children[0];
232
+
233
+ expect(rootSource).toBe(layerRoot.flowHandle.dataSource);
234
+ expect(childWithSource.flowHandle.dataSource).not.toBe(rootSource);
235
+ });
236
+
237
+ test("loadViewSubtreeData only loads nearest sources", async () => {
238
+ const context = createTestViewContext();
239
+ context.addBroadcastListener = () => undefined;
240
+ context.removeBroadcastListener = () => undefined;
241
+
242
+ /** @type {import("../spec/view.js").LayerSpec} */
243
+ const spec = {
244
+ data: { values: [{ x: 0 }] },
245
+ layer: [
246
+ {
247
+ data: { values: [{ x: 1 }] },
248
+ mark: "point",
249
+ encoding: {
250
+ x: { field: "x", type: "quantitative" },
251
+ },
252
+ },
253
+ ],
254
+ };
255
+
256
+ const root = await context.createOrImportView(spec, null, null, "root");
257
+ initializeViewSubtree(root, context.dataFlow);
258
+
259
+ const layerRoot =
260
+ /** @type {import("../view/layerView.js").default} */ (root);
261
+ const rootSource = layerRoot.flowHandle.dataSource;
262
+ const childSource = layerRoot.children[0].flowHandle.dataSource;
263
+
264
+ const rootLoadSpy = vi.spyOn(rootSource, "load");
265
+ const childLoadSpy = vi.spyOn(childSource, "load");
266
+
267
+ // Data-ready should ignore nested sources.
268
+ await loadViewSubtreeData(root);
269
+
270
+ expect(rootLoadSpy).toHaveBeenCalledTimes(1);
271
+ expect(childLoadSpy).toHaveBeenCalledTimes(0);
272
+
273
+ rootLoadSpy.mockRestore();
274
+ childLoadSpy.mockRestore();
275
+ });
276
+
277
+ test("collectViewSubtreeDataSources includes nested sources", async () => {
278
+ const context = createTestViewContext();
279
+ context.addBroadcastListener = () => undefined;
280
+ context.removeBroadcastListener = () => undefined;
281
+
282
+ /** @type {import("../spec/view.js").LayerSpec} */
283
+ const spec = {
284
+ data: { values: [{ x: 0 }] },
285
+ layer: [
286
+ {
287
+ data: { values: [{ x: 1 }] },
288
+ mark: "point",
289
+ encoding: {
290
+ x: { field: "x", type: "quantitative" },
291
+ },
292
+ },
293
+ ],
294
+ };
295
+
296
+ const root = await context.createOrImportView(spec, null, null, "root");
297
+ initializeViewSubtree(root, context.dataFlow);
298
+
299
+ // Initialization needs the full set of sources, including nested ones.
300
+ const sources = collectViewSubtreeDataSources(root);
301
+ expect(sources.size).toBe(2);
302
+ });
303
+
304
+ test("collectNearestViewSubtreeDataSources returns child sources when root has none", async () => {
305
+ const context = createTestViewContext();
306
+ context.addBroadcastListener = () => undefined;
307
+ context.removeBroadcastListener = () => undefined;
308
+
309
+ /** @type {import("../spec/view.js").HConcatSpec} */
310
+ const spec = {
311
+ hconcat: [
312
+ {
313
+ data: { values: [{ x: 1 }] },
314
+ mark: "point",
315
+ encoding: {
316
+ x: { field: "x", type: "quantitative" },
317
+ },
318
+ },
319
+ {
320
+ data: { values: [{ x: 2 }] },
321
+ mark: "point",
322
+ encoding: {
323
+ x: { field: "x", type: "quantitative" },
324
+ },
325
+ },
326
+ ],
327
+ };
328
+
329
+ const root = await context.createOrImportView(spec, null, null, "root");
330
+ initializeViewSubtree(root, context.dataFlow);
331
+
332
+ // Without a root source, the nearest sources include the child sources.
333
+ // Layout decorations may add additional sources.
334
+ const sources = collectNearestViewSubtreeDataSources(root);
335
+ const concatRoot =
336
+ /** @type {import("../view/concatView.js").default} */ (root);
337
+ expect(sources.has(concatRoot.children[0].flowHandle.dataSource)).toBe(
338
+ true
339
+ );
340
+ expect(sources.has(concatRoot.children[1].flowHandle.dataSource)).toBe(
341
+ true
342
+ );
343
+ });
344
+
345
+ test("loadViewSubtreeData emits subtree data ready broadcast", async () => {
346
+ const context = createTestViewContext();
347
+ context.addBroadcastListener = () => undefined;
348
+ context.removeBroadcastListener = () => undefined;
349
+
350
+ /** @type {import("../spec/view.js").UnitSpec} */
351
+ const spec = {
352
+ data: { values: [{ x: 1 }] },
353
+ mark: "point",
354
+ encoding: {
355
+ x: { field: "x", type: "quantitative" },
356
+ },
357
+ };
358
+
359
+ const root = await context.createOrImportView(spec, null, null, "root");
360
+ initializeViewSubtree(root, context.dataFlow);
361
+
362
+ let calls = 0;
363
+ root._addBroadcastHandler("subtreeDataReady", (message) => {
364
+ calls += 1;
365
+ expect(message.payload.subtreeRoot).toBe(root);
366
+ });
367
+
368
+ await loadViewSubtreeData(root);
369
+
370
+ expect(calls).toBe(1);
371
+ });
372
+
373
+ test("loadViewSubtreeData deduplicates concurrent loads", async () => {
374
+ const context = createTestViewContext();
375
+ context.getNamedDataFromProvider = () => [{ x: 1 }];
376
+ context.addBroadcastListener = () => undefined;
377
+ context.removeBroadcastListener = () => undefined;
378
+
379
+ /** @type {import("../spec/view.js").HConcatSpec} */
380
+ const spec = {
381
+ data: { name: "shared" },
382
+ hconcat: [
383
+ {
384
+ mark: "point",
385
+ encoding: {
386
+ x: { field: "x", type: "quantitative" },
387
+ },
388
+ },
389
+ {
390
+ mark: "point",
391
+ encoding: {
392
+ x: { field: "x", type: "quantitative" },
393
+ },
394
+ },
395
+ ],
396
+ };
397
+
398
+ const root = await context.createOrImportView(spec, null, null, "root");
399
+ initializeViewSubtree(root, context.dataFlow);
400
+
401
+ const dataSource = root.flowHandle.dataSource;
402
+ const loadSpy = vi.spyOn(dataSource, "load");
403
+
404
+ // Prevent duplicate fetches when concurrent subtrees share a source.
405
+ await Promise.all([
406
+ loadViewSubtreeData(root, new Set([dataSource])),
407
+ loadViewSubtreeData(root, new Set([dataSource])),
408
+ ]);
409
+
410
+ expect(loadSpy).toHaveBeenCalledTimes(1);
411
+ loadSpy.mockRestore();
412
+ });
413
+ });
@@ -12,16 +12,18 @@ export function removeRedundantCloneTransforms(node: import("./flowNode.js").def
12
12
  export function removeRedundantCollectors(): void;
13
13
  export function combineAndPullCollectorsUp(): void;
14
14
  /**
15
- * @param {import("./dataFlow.js").default<any>} dataFlow
15
+ * @param {import("./dataFlow.js").default} dataFlow
16
+ * @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
16
17
  */
17
- export function combineIdenticalDataSources(dataFlow: import("./dataFlow.js").default<any>): void;
18
+ export function combineIdenticalDataSources(dataFlow: import("./dataFlow.js").default): Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>;
18
19
  /**
19
20
  *
20
21
  * @param {import("./flowNode.js").default} root
21
22
  */
22
23
  export function optimizeFlowGraph(root: import("./flowNode.js").default): void;
23
24
  /**
24
- * @param {import("./dataFlow.js").default<any>} dataFlow
25
+ * @param {import("./dataFlow.js").default} dataFlow
26
+ * @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
25
27
  */
26
- export function optimizeDataFlow(dataFlow: import("./dataFlow.js").default<any>): void;
28
+ export function optimizeDataFlow(dataFlow: import("./dataFlow.js").default): Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>;
27
29
  //# sourceMappingURL=flowOptimizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flowOptimizer.d.ts","sourceRoot":"","sources":["../../../src/data/flowOptimizer.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,oCAHW,OAAO,eAAe,EAAE,OAAO,WAC/B,OAAO,eAAe,EAAE,OAAO,WAczC;AAED;;;;GAIG;AACH,qDAFW,OAAO,eAAe,EAAE,OAAO,iCAgCzC;AAED,kDAEC;AAED,mDAEC;AAmBD;;GAEG;AACH,sDAFW,OAAO,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,QAwB9C;AAED;;;GAGG;AACH,wCAFW,OAAO,eAAe,EAAE,OAAO,QASzC;AAED;;GAEG;AACH,2CAFW,OAAO,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,QAO9C"}
1
+ {"version":3,"file":"flowOptimizer.d.ts","sourceRoot":"","sources":["../../../src/data/flowOptimizer.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,oCAHW,OAAO,eAAe,EAAE,OAAO,WAC/B,OAAO,eAAe,EAAE,OAAO,WAczC;AAED;;;;GAIG;AACH,qDAFW,OAAO,eAAe,EAAE,OAAO,iCAgCzC;AAED,kDAEC;AAED,mDAEC;AAmBD;;;GAGG;AACH,sDAHW,OAAO,eAAe,EAAE,OAAO,GAC7B,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,EAAE,OAAO,yBAAyB,EAAE,OAAO,CAAC,CAoCrG;AAED;;;GAGG;AACH,wCAFW,OAAO,eAAe,EAAE,OAAO,QASzC;AAED;;;GAGG;AACH,2CAHW,OAAO,eAAe,EAAE,OAAO,GAC7B,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,EAAE,OAAO,yBAAyB,EAAE,OAAO,CAAC,CAQrG"}
@@ -82,30 +82,43 @@ export function combineAndPullCollectorsUp() {
82
82
  // --F--G
83
83
 
84
84
  /**
85
- * @param {import("./dataFlow.js").default<any>} dataFlow
85
+ * @param {import("./dataFlow.js").default} dataFlow
86
+ * @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
86
87
  */
87
88
  export function combineIdenticalDataSources(dataFlow) {
88
- const dataSourceEntries = [...dataFlow._dataSourcesByHost.entries()];
89
+ const dataSources = dataFlow.dataSources;
89
90
 
90
91
  /** @type {Map<string, import("./sources/dataSource.js").default>} */
91
92
  const sourcesByIdentifiers = new Map();
92
- for (const e of dataSourceEntries) {
93
- const ds = e[1];
93
+ for (const ds of dataSources) {
94
94
  if (ds.identifier && !sourcesByIdentifiers.has(ds.identifier)) {
95
95
  sourcesByIdentifiers.set(ds.identifier, ds);
96
96
  }
97
97
  }
98
98
 
99
- dataFlow._dataSourcesByHost.clear();
100
-
101
- for (let [key, dataSource] of dataSourceEntries) {
102
- const target = sourcesByIdentifiers.get(dataSource.identifier);
103
- if (target) {
104
- target.adoptChildrenOf(dataSource);
105
- dataSource = target;
99
+ /** @type {Set<import("./sources/dataSource.js").default>} */
100
+ const mergedSources = new Set();
101
+ /** @type {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>} */
102
+ const canonicalBySource = new Map();
103
+
104
+ for (const dataSource of dataSources) {
105
+ if (dataSource.identifier) {
106
+ const target = sourcesByIdentifiers.get(dataSource.identifier);
107
+ if (target) {
108
+ if (target !== dataSource) {
109
+ target.adoptChildrenOf(dataSource);
110
+ }
111
+ mergedSources.add(target);
112
+ canonicalBySource.set(dataSource, target);
113
+ }
114
+ } else {
115
+ mergedSources.add(dataSource);
116
+ canonicalBySource.set(dataSource, dataSource);
106
117
  }
107
- dataFlow.addDataSource(dataSource, key);
108
118
  }
119
+
120
+ dataFlow.replaceDataSources(mergedSources);
121
+ return canonicalBySource;
109
122
  }
110
123
 
111
124
  /**
@@ -122,11 +135,13 @@ export function optimizeFlowGraph(root) {
122
135
  }
123
136
 
124
137
  /**
125
- * @param {import("./dataFlow.js").default<any>} dataFlow
138
+ * @param {import("./dataFlow.js").default} dataFlow
139
+ * @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
126
140
  */
127
141
  export function optimizeDataFlow(dataFlow) {
128
- combineIdenticalDataSources(dataFlow);
142
+ const canonicalBySource = combineIdenticalDataSources(dataFlow);
129
143
  for (const dataSource of dataFlow.dataSources) {
130
144
  optimizeFlowGraph(dataSource);
131
145
  }
146
+ return canonicalBySource;
132
147
  }
@@ -142,7 +142,7 @@ const viewStub = /** @type {any} */ (
142
142
 
143
143
  describe("Merge indentical data sources", () => {
144
144
  test("Merges correctly", () => {
145
- /** @type {DataFlow<string>} */
145
+ /** @type {DataFlow} */
146
146
  const dataFlow = new DataFlow();
147
147
 
148
148
  const a = new UrlSource({ url: "http://genomespy.app/" }, viewStub);
@@ -157,21 +157,25 @@ describe("Merge indentical data sources", () => {
157
157
  const cc = new Collector();
158
158
  c.addChild(cc);
159
159
 
160
- dataFlow.addDataSource(a, "a");
161
- dataFlow.addDataSource(b, "b");
162
- dataFlow.addDataSource(c, "c");
160
+ dataFlow.addDataSource(a);
161
+ dataFlow.addDataSource(b);
162
+ dataFlow.addDataSource(c);
163
163
 
164
- dataFlow.addCollector(ac, "a");
165
- dataFlow.addCollector(bc, "b");
166
- dataFlow.addCollector(cc, "c");
164
+ dataFlow.addCollector(ac);
165
+ dataFlow.addCollector(bc);
166
+ dataFlow.addCollector(cc);
167
167
 
168
168
  combineIdenticalDataSources(dataFlow);
169
169
 
170
170
  expect(dataFlow.dataSources.length).toEqual(2);
171
171
 
172
- expect(dataFlow.findDataSourceByKey("a")).toBe(a);
173
- expect(dataFlow.findDataSourceByKey("b")).toBe(a); // Merged!
174
- expect(dataFlow.findDataSourceByKey("c")).toBe(c);
172
+ const entries = dataFlow.dataSources;
173
+ const sharedIdentifier = a.identifier;
174
+ expect(entries).toContain(a);
175
+ expect(entries).toContain(c);
176
+ expect(
177
+ entries.filter((source) => source.identifier == sharedIdentifier)
178
+ ).toEqual([a]);
175
179
 
176
180
  expect(new Set(a.children)).toEqual(new Set([ac, bc]));
177
181
  expect(c.children[0]).toBe(cc);
@@ -187,18 +191,19 @@ describe("Merge indentical data sources", () => {
187
191
  });
188
192
 
189
193
  test("Does not merge those with undefined identifier", () => {
190
- /** @type {DataFlow<string>} */
194
+ /** @type {DataFlow} */
191
195
  const dataFlow = new DataFlow();
192
196
 
193
197
  const a = new InlineSource({ values: [1, 2, 3] }, viewStub);
194
198
  const b = new InlineSource({ values: [1, 2, 3] }, viewStub);
195
199
 
196
- dataFlow.addDataSource(a, "a");
197
- dataFlow.addDataSource(b, "b");
200
+ dataFlow.addDataSource(a);
201
+ dataFlow.addDataSource(b);
198
202
 
199
203
  combineIdenticalDataSources(dataFlow);
200
204
 
201
- expect(dataFlow.findDataSourceByKey("a")).toBe(a);
202
- expect(dataFlow.findDataSourceByKey("b")).toBe(b);
205
+ const entries = dataFlow.dataSources;
206
+ expect(entries).toContain(a);
207
+ expect(entries).toContain(b);
203
208
  });
204
209
  });
@@ -43,7 +43,7 @@ export default class BamSource extends SingleAxisWindowedSource {
43
43
  this.initializedPromise = new Promise((resolve) => {
44
44
  Promise.all([
45
45
  import("@gmod/bam"),
46
- import("generic-filehandle"),
46
+ import("generic-filehandle2"),
47
47
  ]).then(([{ BamFile }, { RemoteFile }]) => {
48
48
  const withBase = (/** @type {string} */ uri) =>
49
49
  new RemoteFile(addBaseUrl(uri, this.view.getBaseUrl()));
@@ -63,7 +63,7 @@ export default class BigBedSource extends SingleAxisWindowedSource {
63
63
  Promise.all([
64
64
  import("@gmod/bed"),
65
65
  import("@gmod/bbi"),
66
- import("generic-filehandle"),
66
+ import("generic-filehandle2"),
67
67
  ]).then(([bed, { BigBed }, { RemoteFile }]) => {
68
68
  const BED = bed.default;
69
69
 
@@ -62,7 +62,7 @@ export default class BigWigSource extends SingleAxisWindowedSource {
62
62
  this.initializedPromise = new Promise((resolve, reject) => {
63
63
  Promise.all([
64
64
  import("@gmod/bbi"),
65
- import("generic-filehandle"),
65
+ import("generic-filehandle2"),
66
66
  ]).then(([{ BigWig }, { RemoteFile }]) => {
67
67
  this.#bbi = new BigWig({
68
68
  filehandle: new RemoteFile(
@@ -1,12 +1,8 @@
1
1
  /**
2
- * @extends {TabixSource<import("@gmod/gff").GFF3Feature>}
2
+ * @extends {TabixSource<import("gff-nostream").GFF3Feature>}
3
3
  */
4
- export default class Gff3Source extends TabixSource<import("@gmod/gff").GFF3Feature> {
4
+ export default class Gff3Source extends TabixSource<import("gff-nostream").GFF3Feature> {
5
5
  constructor(params: import("../../../spec/data.js").TabixData, view: import("../../../view/view.js").default);
6
- /**
7
- * @param {string[]} lines
8
- */
9
- _parseFeatures(lines: string[]): any;
10
6
  #private;
11
7
  }
12
8
  import TabixSource from "./tabixSource.js";
@@ -1 +1 @@
1
- {"version":3,"file":"gff3Source.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/gff3Source.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;;IAeI;;OAEG;IACH,sBAFW,MAAM,EAAE,OAUlB;;CACJ;wBAhCuB,kBAAkB"}
1
+ {"version":3,"file":"gff3Source.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/gff3Source.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;;;CAuBC;wBA5BuB,kBAAkB"}
@@ -1,10 +1,10 @@
1
1
  import TabixSource from "./tabixSource.js";
2
2
 
3
3
  /**
4
- * @extends {TabixSource<import("@gmod/gff").GFF3Feature>}
4
+ * @extends {TabixSource<import("gff-nostream").GFF3Feature>}
5
5
  */
6
6
  export default class Gff3Source extends TabixSource {
7
- /** @type {import("@gmod/gff").default} */
7
+ /** @type {import("gff-nostream")} */
8
8
  #gff;
9
9
 
10
10
  get label() {
@@ -15,18 +15,14 @@ export default class Gff3Source extends TabixSource {
15
15
  * @param {string} header
16
16
  */
17
17
  async _handleHeader(header) {
18
- this.#gff = (await import("@gmod/gff")).default;
18
+ this.#gff = await import("gff-nostream");
19
19
  }
20
20
 
21
21
  /**
22
22
  * @param {string[]} lines
23
23
  */
24
24
  _parseFeatures(lines) {
25
- // Hmm. It's silly that we have to first collect individual lines and then join them.
26
- // eslint-disable-next-line no-sync
27
- const features = this.#gff?.parseStringSync(lines.join("\n"), {
28
- parseSequences: false,
29
- });
25
+ const features = this.#gff?.parseArraySync(lines);
30
26
 
31
27
  return features;
32
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"indexedFastaSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/indexedFastaSource.js"],"names":[],"mappings":"AAGA;IACI;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,gBAAgB,QAChD,OAAO,uBAAuB,EAAE,OAAO,EA+CjD;IAjCG,yDAAgC;IAQhC,iCAwBE;IATM,iDAKE;CAiCjB;qCAhFoC,+BAA+B"}
1
+ {"version":3,"file":"indexedFastaSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/indexedFastaSource.js"],"names":[],"mappings":"AAGA;IACI;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,gBAAgB,QAChD,OAAO,uBAAuB,EAAE,OAAO,EAwCjD;IA1BG,yDAAgC;IAQhC,iCAiBE;IATM,iDAKE;CAwCjB;qCAhFoC,+BAA+B"}