loom-browser 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/loom-react.esm.js +3877 -3173
  2. package/dist/loom-react.esm.min.js +1 -1
  3. package/dist/loom-react.esm.min.js.map +1 -1
  4. package/dist/loom-worker.js +191 -137
  5. package/dist/loom-worker.min.js +1 -1
  6. package/dist/loom-worker.min.js.map +1 -1
  7. package/dist/loom.esm.js +4163 -3412
  8. package/dist/loom.esm.min.js +1 -1
  9. package/dist/loom.esm.min.js.map +1 -1
  10. package/dist/loom.js +4174 -3415
  11. package/dist/loom.min.js +1 -1
  12. package/dist/loom.min.js.map +1 -1
  13. package/dist/tsconfig.src.tsbuildinfo +1 -1
  14. package/dist/types/browser/dom/browserExport.d.ts +32 -0
  15. package/dist/types/browser/dom/contextMenu.d.ts +64 -0
  16. package/dist/types/browser/dom/contextMenuManager.d.ts +49 -0
  17. package/dist/types/browser/dom/defaultProviders.d.ts +23 -0
  18. package/dist/types/browser/dom/genomeBrowser.d.ts +171 -0
  19. package/dist/types/browser/dom/pointerEventManager.d.ts +109 -0
  20. package/dist/types/browser/dom/roiOverlayManager.d.ts +38 -0
  21. package/dist/types/browser/dom/svgFeatureOverlay.d.ts +27 -0
  22. package/dist/types/browser/headless/errors.d.ts +10 -0
  23. package/dist/types/browser/headless/headlessGenomeBrowser.d.ts +391 -0
  24. package/dist/types/browser/headless/roiManager.d.ts +47 -0
  25. package/dist/types/browser/headless/session.d.ts +45 -0
  26. package/dist/types/browser/headless/trackDataManager.d.ts +48 -0
  27. package/dist/types/browser/headless/trackFactories.d.ts +145 -0
  28. package/dist/types/browser/index.d.ts +24 -0
  29. package/dist/types/browserExport.d.ts +32 -0
  30. package/dist/types/commandDispatcher.d.ts +1 -1
  31. package/dist/types/contextMenuManager.d.ts +49 -0
  32. package/dist/types/data/searchService.d.ts +24 -0
  33. package/dist/types/dataSourceWorkerProvider.d.ts +3 -3
  34. package/dist/types/dataSources/bigWigDataSource.d.ts +2 -2
  35. package/dist/types/dataSources/config.d.ts +40 -0
  36. package/dist/types/dataSources/configureDataSource.d.ts +17 -0
  37. package/dist/types/dataSources/createDataSource.d.ts +14 -0
  38. package/dist/types/dataSources/geneDataSource.d.ts +4 -3
  39. package/dist/types/dataSources/gtxDataSource.d.ts +2 -2
  40. package/dist/types/dataSources/memoryDataSource.d.ts +2 -2
  41. package/dist/types/dataSources/sequenceDataSource.d.ts +4 -1
  42. package/dist/types/dataSources/textFeatureSource.d.ts +4 -2
  43. package/dist/types/dataSources/wholeGenomeUtils.d.ts +37 -0
  44. package/dist/types/defaultProviders.d.ts +23 -0
  45. package/dist/types/errors.d.ts +10 -0
  46. package/dist/types/formats/featureParser.d.ts +1 -1
  47. package/dist/types/genomeBrowser.d.ts +18 -91
  48. package/dist/types/headlessGenomeBrowser.d.ts +88 -225
  49. package/dist/types/index.d.ts +21 -14
  50. package/dist/types/io/binaryParser.d.ts +0 -1
  51. package/dist/types/logger.d.ts +20 -0
  52. package/dist/types/pointerEventManager.d.ts +109 -0
  53. package/dist/types/react/GenomeBrowserContext.d.ts +1 -1
  54. package/dist/types/react/LoomBrowser.d.ts +4 -4
  55. package/dist/types/react/hooks/useBrowserEvent.d.ts +1 -1
  56. package/dist/types/react/hooks/useGenomeBrowser.d.ts +1 -1
  57. package/dist/types/react/hooks/useTrackManager.d.ts +1 -1
  58. package/dist/types/react/ui/ChromosomeSelect.d.ts +1 -1
  59. package/dist/types/react/ui/ExportControls.d.ts +1 -1
  60. package/dist/types/react/ui/LocusInput.d.ts +1 -1
  61. package/dist/types/react/ui/Navbar.d.ts +1 -1
  62. package/dist/types/react/ui/WindowSize.d.ts +1 -1
  63. package/dist/types/react/ui/ZoomControls.d.ts +1 -1
  64. package/dist/types/remoteProtocol.d.ts +1 -1
  65. package/dist/types/roiManager.d.ts +47 -0
  66. package/dist/types/roiOverlayManager.d.ts +38 -0
  67. package/dist/types/stateProjection.d.ts +1 -1
  68. package/dist/types/trackDataManager.d.ts +48 -0
  69. package/dist/types/trackFactories.d.ts +140 -0
  70. package/dist/types/trackSelector.d.ts +1 -1
  71. package/dist/types/tracks/annotation/annotationTrackCanvas.d.ts +0 -1
  72. package/dist/types/tracks/annotation/config.d.ts +61 -0
  73. package/dist/types/tracks/baseTrackCanvas.d.ts +10 -0
  74. package/dist/types/tracks/configDiff.d.ts +14 -0
  75. package/dist/types/tracks/interaction/config.d.ts +40 -0
  76. package/dist/types/tracks/ruler/config.d.ts +24 -0
  77. package/dist/types/tracks/sequence/config.d.ts +56 -0
  78. package/dist/types/tracks/sequence/sequenceTrackCanvas.d.ts +3 -0
  79. package/dist/types/tracks/wig/config.d.ts +77 -0
  80. package/dist/types/types/igvCompat.d.ts +36 -0
  81. package/dist/types/types.d.ts +48 -284
  82. package/dist/types/ui/components/LoomBrowserShell.d.ts +2 -2
  83. package/dist/types/ui/components/LoomChromosomeSelect.d.ts +1 -1
  84. package/dist/types/ui/components/LoomContextMenu.d.ts +1 -0
  85. package/dist/types/ui/components/LoomExportControls.d.ts +1 -1
  86. package/dist/types/ui/components/LoomLocusInput.d.ts +1 -1
  87. package/dist/types/ui/components/LoomNavbar.d.ts +1 -1
  88. package/dist/types/ui/components/LoomWindowSize.d.ts +1 -1
  89. package/dist/types/ui/components/LoomZoomControls.d.ts +1 -1
  90. package/dist/types/undoManager.d.ts +49 -0
  91. package/dist/types/worker/dataSourceRegistry.d.ts +2 -2
  92. package/dist/types/worker/serializedError.d.ts +16 -0
  93. package/dist/types/worker/taskTimeout.d.ts +21 -0
  94. package/dist/types/worker/webWorkerPool.d.ts +5 -2
  95. package/dist/types/worker/webWorkerProvider.d.ts +3 -0
  96. package/dist/types/workerDataSource.d.ts +2 -2
  97. package/dist/types/workerProvider.d.ts +2 -2
  98. package/package.json +1 -1
@@ -1600,6 +1600,28 @@ async function handleTask(task) {
1600
1600
  }
1601
1601
  }
1602
1602
 
1603
+ /**
1604
+ * Type-safe method dispatch for DataSource configuration.
1605
+ *
1606
+ * Centralizes setter calls by name (e.g., setCumulativeOffsets, setWindowFunction)
1607
+ * on DataSource instances that may or may not implement ConfigurableDataSource.
1608
+ *
1609
+ * Previously used unsafe `Record<string, unknown>` casts — now constrained to
1610
+ * known method names via `DataSourceConfigMethod`.
1611
+ *
1612
+ * Layer 1 (Data): no DOM, no canvas.
1613
+ */
1614
+ /**
1615
+ * Call a named configuration setter on a DataSource if it exists.
1616
+ * The method name is constrained to known ConfigurableDataSource methods.
1617
+ */
1618
+ function configureDataSource(ds, method, ...args) {
1619
+ const fn = ds[method];
1620
+ if (typeof fn === 'function') {
1621
+ fn.call(ds, ...args);
1622
+ }
1623
+ }
1624
+
1603
1625
  // from https://github.com/yume-chan/ya-webadb/blob/main/libraries/dataview-bigint-polyfill
1604
1626
  // license:MIT
1605
1627
  // needed for browsers including safari 14
@@ -3688,9 +3710,6 @@ class BinaryParser {
3688
3710
  available() {
3689
3711
  return this.length - this.position;
3690
3712
  }
3691
- remLength() {
3692
- return this.length - this.position;
3693
- }
3694
3713
  getByte() {
3695
3714
  const retValue = this.view.getUint8(this.position);
3696
3715
  this.position++;
@@ -3914,6 +3933,62 @@ function isWholeGenomeView(locus) {
3914
3933
  return locus.chr.toLowerCase() === 'all';
3915
3934
  }
3916
3935
 
3936
+ /**
3937
+ * Whole-genome coordinate transform utilities.
3938
+ *
3939
+ * Shared logic for converting per-chromosome features to genome-wide
3940
+ * coordinates using CumulativeOffsets. Used by all DataSource implementations
3941
+ * that support whole-genome view.
3942
+ *
3943
+ * Layer 1 (Data): no DOM, no canvas.
3944
+ */
3945
+ /**
3946
+ * Transform per-chromosome features to genome-wide coordinates.
3947
+ *
3948
+ * For each feature, replaces `chr` with `'all'` and shifts `start`/`end`
3949
+ * by the chromosome's cumulative offset. Features on unknown chromosomes
3950
+ * are silently dropped. Results are sorted by start position.
3951
+ */
3952
+ /**
3953
+ * Fetch features for all chromosomes in parallel, tolerating individual failures.
3954
+ *
3955
+ * Uses `Promise.allSettled` so a single chromosome failure doesn't abort the
3956
+ * entire whole-genome view. If ALL chromosomes fail, the first error is re-thrown.
3957
+ *
3958
+ * @param chrNames - Chromosome names to fetch
3959
+ * @param fetchChrom - Per-chromosome fetch function
3960
+ * @param signal - AbortSignal for cancellation
3961
+ * @returns Flattened array of features from all successful chromosome fetches
3962
+ */
3963
+ async function fetchAllChromosomes(chrNames, fetchChrom, signal) {
3964
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted)
3965
+ return [];
3966
+ const results = await Promise.allSettled(chrNames.map(chr => fetchChrom(chr)));
3967
+ const fulfilled = results.filter((r) => r.status === 'fulfilled');
3968
+ if (fulfilled.length === 0 && results.length > 0) {
3969
+ throw results[0].reason;
3970
+ }
3971
+ return fulfilled.flatMap(r => r.value);
3972
+ }
3973
+ function transformToWholeGenome(features, offsets) {
3974
+ const result = [];
3975
+ for (const f of features) {
3976
+ if (!f.chr)
3977
+ continue;
3978
+ const offset = offsets.offsets[f.chr];
3979
+ if (offset === undefined)
3980
+ continue;
3981
+ result.push({
3982
+ ...f,
3983
+ chr: 'all',
3984
+ start: offset + f.start,
3985
+ end: offset + f.end,
3986
+ });
3987
+ }
3988
+ result.sort((a, b) => a.start - b.start);
3989
+ return result;
3990
+ }
3991
+
3917
3992
  class BigWigDataSource {
3918
3993
  constructor(url, windowFunction = 'mean') {
3919
3994
  this.url = url;
@@ -3957,21 +4032,10 @@ class BigWigDataSource {
3957
4032
  */
3958
4033
  async fetchWG(bpPerPixel, signal) {
3959
4034
  const offsets = this._cumulativeOffsets;
4035
+ if (!offsets)
4036
+ throw new Error('fetchWG called without cumulativeOffsets');
3960
4037
  const features = await fetchBigWigWGFeatures(this.url, offsets.chromosomeNames, bpPerPixel, { windowFunction: this._windowFunction, signal });
3961
- // Transform to genome-wide coordinates
3962
- const wgFeatures = [];
3963
- for (const f of features) {
3964
- const offset = offsets.offsets[f.chr];
3965
- if (offset === undefined)
3966
- continue;
3967
- wgFeatures.push({
3968
- ...f,
3969
- chr: 'all',
3970
- start: offset + f.start,
3971
- end: offset + f.end,
3972
- });
3973
- }
3974
- wgFeatures.sort((a, b) => a.start - b.start);
4038
+ const wgFeatures = transformToWholeGenome(features, offsets);
3975
4039
  // Summarize at genome-wide scale
3976
4040
  if (bpPerPixel > 1 && this._windowFunction !== 'none' && wgFeatures.length > 0) {
3977
4041
  return summarizeWigData(wgFeatures, 0, bpPerPixel, this._windowFunction);
@@ -4972,21 +5036,10 @@ class GtxDataSource {
4972
5036
  */
4973
5037
  async fetchWG(bpPerPixel, signal) {
4974
5038
  const offsets = this._cumulativeOffsets;
5039
+ if (!offsets)
5040
+ throw new Error('fetchWG called without cumulativeOffsets');
4975
5041
  const features = await fetchGtxWGFeatures(this.url, this.experimentId, offsets.chromosomeNames, bpPerPixel, { windowFunction: this._windowFunction, signal });
4976
- // Transform to genome-wide coordinates
4977
- const wgFeatures = [];
4978
- for (const f of features) {
4979
- const offset = offsets.offsets[f.chr];
4980
- if (offset === undefined)
4981
- continue;
4982
- wgFeatures.push({
4983
- ...f,
4984
- chr: 'all',
4985
- start: offset + f.start,
4986
- end: offset + f.end,
4987
- });
4988
- }
4989
- wgFeatures.sort((a, b) => a.start - b.start);
5042
+ const wgFeatures = transformToWholeGenome(features, offsets);
4990
5043
  // Summarize at genome-wide scale
4991
5044
  if (bpPerPixel > 1 && this._windowFunction !== 'none' && wgFeatures.length > 0) {
4992
5045
  return summarizeWigData(wgFeatures, 0, bpPerPixel, this._windowFunction);
@@ -5023,11 +5076,12 @@ async function fetchGeneFeatures(locus, options = {}, signal) {
5023
5076
  return records.map(decodeRefGeneJson);
5024
5077
  }
5025
5078
 
5026
- /** Maximum number of WG features to avoid rendering performance issues (reservoir sampling). */
5027
- const MAX_WG_FEATURES = 10000;
5079
+ /** Default max WG features — reservoir-sampled to avoid rendering performance issues. */
5080
+ const DEFAULT_MAX_WG_FEATURES = 10000;
5028
5081
  class GeneDataSource {
5029
- constructor(options = {}) {
5082
+ constructor(options = {}, maxWgFeatures = DEFAULT_MAX_WG_FEATURES) {
5030
5083
  this.options = options;
5084
+ this.maxWgFeatures = maxWgFeatures;
5031
5085
  }
5032
5086
  /** Set cumulative offsets for whole genome view coordinate transformation. */
5033
5087
  setCumulativeOffsets(offsets) {
@@ -5045,34 +5099,17 @@ class GeneDataSource {
5045
5099
  */
5046
5100
  async fetchWG(signal) {
5047
5101
  const offsets = this._cumulativeOffsets;
5048
- // Fetch all chromosomes in parallel
5049
- const promises = offsets.chromosomeNames.map(chr => fetchGeneFeatures({ chr, start: 0, end: Number.MAX_SAFE_INTEGER }, this.options, signal)
5050
- .catch(() => []) // Skip chromosomes that fail
5051
- );
5052
- const perChromResults = await Promise.all(promises);
5053
- // Transform to genome-wide coordinates, strip exons for WG scale
5054
- const wgFeatures = [];
5055
- for (let i = 0; i < offsets.chromosomeNames.length; i++) {
5056
- const chr = offsets.chromosomeNames[i];
5057
- const offset = offsets.offsets[chr];
5058
- if (offset === undefined)
5059
- continue;
5060
- for (const f of perChromResults[i]) {
5061
- wgFeatures.push({
5062
- ...f,
5063
- chr: 'all',
5064
- start: offset + f.start,
5065
- end: offset + f.end,
5066
- // Strip detailed structure — too zoomed out
5067
- exons: undefined,
5068
- cdStart: undefined,
5069
- cdEnd: undefined,
5070
- });
5071
- }
5072
- }
5102
+ if (!offsets)
5103
+ throw new Error('fetchWG called without cumulativeOffsets');
5104
+ const allFeatures = await fetchAllChromosomes(offsets.chromosomeNames, chr => fetchGeneFeatures({ chr, start: 0, end: Number.MAX_SAFE_INTEGER }, this.options, signal), signal);
5105
+ // Strip detailed structure (too zoomed out for exons)
5106
+ const flat = allFeatures.map(f => ({
5107
+ ...f, exons: undefined, cdStart: undefined, cdEnd: undefined,
5108
+ }));
5109
+ const wgFeatures = transformToWholeGenome(flat, offsets);
5073
5110
  // Downsample with reservoir sampling if too many features
5074
- if (wgFeatures.length > MAX_WG_FEATURES) {
5075
- return reservoirSample(wgFeatures, MAX_WG_FEATURES);
5111
+ if (wgFeatures.length > this.maxWgFeatures) {
5112
+ return reservoirSample(wgFeatures, this.maxWgFeatures);
5076
5113
  }
5077
5114
  return wgFeatures;
5078
5115
  }
@@ -10346,6 +10383,9 @@ class TextFeatureSource {
10346
10383
  else {
10347
10384
  text = await response.text();
10348
10385
  }
10386
+ // Check abort before expensive parsing
10387
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted)
10388
+ return;
10349
10389
  const lines = text.split(/\r?\n/);
10350
10390
  // Parse header
10351
10391
  this.header = parseHeader(lines, this.format);
@@ -10366,32 +10406,16 @@ class TextFeatureSource {
10366
10406
  }
10367
10407
  async fetchWG(signal) {
10368
10408
  var _a, _b;
10409
+ if (signal.aborted)
10410
+ return [];
10369
10411
  const offsets = this._cumulativeOffsets;
10412
+ if (!offsets)
10413
+ throw new Error('fetchWG called without cumulativeOffsets');
10370
10414
  if (this._indexed) {
10371
10415
  // For indexed files, fetch all main chromosomes
10372
10416
  const chrNames = mainChromosomeNames(Object.fromEntries(offsets.chromosomeNames.map(name => { var _a; return [name, (_a = offsets.offsets[name]) !== null && _a !== void 0 ? _a : 0]; })));
10373
- const allFeatures = [];
10374
- for (const chrName of chrNames) {
10375
- try {
10376
- const features = await this.fetchIndexed(chrName, 0, Number.MAX_SAFE_INTEGER, signal);
10377
- const offset = offsets.offsets[chrName];
10378
- if (offset === undefined)
10379
- continue;
10380
- for (const f of features) {
10381
- allFeatures.push({
10382
- ...f,
10383
- chr: 'all',
10384
- start: offset + f.start,
10385
- end: offset + f.end,
10386
- });
10387
- }
10388
- }
10389
- catch (_c) {
10390
- // Skip chromosomes that fail
10391
- }
10392
- }
10393
- allFeatures.sort((a, b) => a.start - b.start);
10394
- return allFeatures;
10417
+ const allFeatures = await fetchAllChromosomes(chrNames, chrName => this.fetchIndexed(chrName, 0, Number.MAX_SAFE_INTEGER, signal), signal);
10418
+ return transformToWholeGenome(allFeatures, offsets);
10395
10419
  }
10396
10420
  else {
10397
10421
  // Non-indexed: load all, transform coordinates
@@ -10399,24 +10423,54 @@ class TextFeatureSource {
10399
10423
  await this.loadAllFeatures(signal);
10400
10424
  }
10401
10425
  const allByChrom = (_b = (_a = this.featureCache) === null || _a === void 0 ? void 0 : _a.getAllFeatures()) !== null && _b !== void 0 ? _b : {};
10402
- const wgFeatures = [];
10403
- for (const [chr, features] of Object.entries(allByChrom)) {
10404
- const offset = offsets.offsets[chr];
10405
- if (offset === undefined)
10406
- continue;
10407
- for (const f of features) {
10408
- wgFeatures.push({
10409
- ...f,
10410
- chr: 'all',
10411
- start: offset + f.start,
10412
- end: offset + f.end,
10413
- });
10414
- }
10426
+ const allFeatures = [];
10427
+ for (const features of Object.values(allByChrom)) {
10428
+ allFeatures.push(...features);
10415
10429
  }
10416
- wgFeatures.sort((a, b) => a.start - b.start);
10417
- return wgFeatures;
10430
+ return transformToWholeGenome(allFeatures, offsets);
10418
10431
  }
10419
10432
  }
10433
+ /** Release resources (TabixReader, feature cache). */
10434
+ dispose() {
10435
+ this.tabixReader = undefined;
10436
+ this.featureCache = undefined;
10437
+ this.allFeaturesLoaded = false;
10438
+ }
10439
+ }
10440
+
10441
+ /**
10442
+ * Factory: create a DataSource from a serializable DataSourceConfig.
10443
+ *
10444
+ * Shared by MainThreadProvider and DataSourceRegistry (worker-side) to
10445
+ * avoid duplicating the config → DataSource mapping logic.
10446
+ *
10447
+ * Does NOT handle 'memory' or 'sequence' configs — those require
10448
+ * non-serializable inputs (in-memory features, SequenceProvider function)
10449
+ * and must stay on the main thread.
10450
+ *
10451
+ * Layer 1 (Data): no DOM, no canvas.
10452
+ */
10453
+ function createDataSource(config) {
10454
+ var _a, _b;
10455
+ switch (config.type) {
10456
+ case 'bigwig':
10457
+ return new BigWigDataSource(config.url, (_a = config.windowFunction) !== null && _a !== void 0 ? _a : 'mean');
10458
+ case 'gtx':
10459
+ return new GtxDataSource(config.url, config.experimentId, (_b = config.windowFunction) !== null && _b !== void 0 ? _b : 'mean');
10460
+ case 'ucsc':
10461
+ return new GeneDataSource({ genome: config.genome, track: config.track });
10462
+ case 'text':
10463
+ return new TextFeatureSource({
10464
+ url: config.url,
10465
+ format: config.format,
10466
+ indexURL: config.indexURL,
10467
+ indexed: config.indexed,
10468
+ });
10469
+ case 'memory':
10470
+ throw new Error('MemoryDataSource cannot be created via provider — use MemoryDataSource directly');
10471
+ default:
10472
+ throw new Error(`Unknown DataSourceConfig type: ${config.type}`);
10473
+ }
10420
10474
  }
10421
10475
 
10422
10476
  /**
@@ -10471,45 +10525,46 @@ class DataSourceRegistry {
10471
10525
  const ds = this.instances.get(instanceId);
10472
10526
  if (!ds)
10473
10527
  return;
10474
- const fn = ds[method];
10475
- if (typeof fn === 'function') {
10476
- fn.call(ds, ...args);
10477
- }
10528
+ configureDataSource(ds, method, ...args);
10478
10529
  }
10479
10530
  /** Destroy a DataSource instance and clean up. */
10480
10531
  destroy(instanceId) {
10532
+ var _a;
10533
+ const ds = this.instances.get(instanceId);
10534
+ (_a = ds === null || ds === void 0 ? void 0 : ds.dispose) === null || _a === void 0 ? void 0 : _a.call(ds);
10481
10535
  this.instances.delete(instanceId);
10482
10536
  }
10483
10537
  }
10538
+
10539
+ /** Extract serializable fields from an error. */
10540
+ function serializeError(err) {
10541
+ if (err instanceof Error) {
10542
+ return { message: err.message, name: err.name, stack: err.stack };
10543
+ }
10544
+ return { message: String(err), name: 'Error' };
10545
+ }
10546
+
10484
10547
  /**
10485
- * Factory: create a DataSource from a serializable DataSourceConfig.
10548
+ * Structured logger for the Loom genome browser.
10486
10549
  *
10487
- * Does NOT handle 'memory' or 'sequence' configs — those require
10488
- * non-serializable inputs (in-memory features, SequenceProvider function)
10489
- * and must stay on the main thread.
10550
+ * Replaces scattered `console.log/warn/error` calls with a level-gated logger
10551
+ * that can be silenced or redirected by consumers.
10552
+ *
10553
+ * Default level: 'warn' — only warnings and errors are logged.
10554
+ * Call `setLogLevel('silent')` to suppress all output in production.
10490
10555
  */
10491
- function createDataSource(config) {
10492
- var _a, _b;
10493
- switch (config.type) {
10494
- case 'bigwig':
10495
- return new BigWigDataSource(config.url, (_a = config.windowFunction) !== null && _a !== void 0 ? _a : 'mean');
10496
- case 'gtx':
10497
- return new GtxDataSource(config.url, config.experimentId, (_b = config.windowFunction) !== null && _b !== void 0 ? _b : 'mean');
10498
- case 'ucsc':
10499
- return new GeneDataSource({ genome: config.genome, track: config.track });
10500
- case 'text':
10501
- return new TextFeatureSource({
10502
- url: config.url,
10503
- format: config.format,
10504
- indexURL: config.indexURL,
10505
- indexed: config.indexed,
10506
- });
10507
- case 'memory':
10508
- throw new Error('MemoryDataSource cannot be created in a worker — requires in-memory features');
10509
- default:
10510
- throw new Error(`Unknown DataSourceConfig type: ${config.type}`);
10511
- }
10512
- }
10556
+ const log = {
10557
+ debug(...args) {
10558
+ },
10559
+ info(...args) {
10560
+ },
10561
+ warn(...args) {
10562
+ console.warn('[loom]', ...args);
10563
+ },
10564
+ error(...args) {
10565
+ console.error('[loom]', ...args);
10566
+ },
10567
+ };
10513
10568
 
10514
10569
  /**
10515
10570
  * Unified Web Worker entry point — handles both stateless tasks and stateful data sources.
@@ -10529,7 +10584,6 @@ function createDataSource(config) {
10529
10584
  */
10530
10585
  const workerSelf = self;
10531
10586
  const registry = new DataSourceRegistry();
10532
- console.log('[loom worker] initialized');
10533
10587
  workerSelf.onmessage = async (e) => {
10534
10588
  const msg = e.data;
10535
10589
  switch (msg.type) {
@@ -10540,11 +10594,13 @@ workerSelf.onmessage = async (e) => {
10540
10594
  if (result instanceof ArrayBuffer) {
10541
10595
  transfer.push(result);
10542
10596
  }
10597
+ else if (ArrayBuffer.isView(result)) {
10598
+ transfer.push(result.buffer);
10599
+ }
10543
10600
  workerSelf.postMessage({ type: 'taskResult', id: msg.id, result }, { transfer });
10544
10601
  }
10545
10602
  catch (err) {
10546
- const message = err instanceof Error ? err.message : String(err);
10547
- workerSelf.postMessage({ type: 'taskResult', id: msg.id, error: message });
10603
+ workerSelf.postMessage({ type: 'taskResult', id: msg.id, error: serializeError(err) });
10548
10604
  }
10549
10605
  break;
10550
10606
  }
@@ -10553,7 +10609,7 @@ workerSelf.onmessage = async (e) => {
10553
10609
  registry.create(msg.instanceId, msg.config);
10554
10610
  }
10555
10611
  catch (err) {
10556
- console.error(`[loom worker] create failed for ${msg.instanceId}:`, err);
10612
+ log.error(`create failed for ${msg.instanceId}:`, err);
10557
10613
  }
10558
10614
  break;
10559
10615
  case 'fetch': {
@@ -10567,12 +10623,10 @@ workerSelf.onmessage = async (e) => {
10567
10623
  }
10568
10624
  catch (err) {
10569
10625
  const message = err instanceof Error ? err.message : String(err);
10570
- // Detect abort errors robustly: DOMException with name 'AbortError',
10571
- // or Error with name 'AbortError', or the browser's native abort
10572
- // message ("signal is aborted without reason" / "The operation was aborted").
10626
+ // Detect abort errors via the standard AbortError name.
10627
+ // Avoid fragile string matching on error messages.
10573
10628
  const isAbort = (err instanceof DOMException && err.name === 'AbortError')
10574
- || (err instanceof Error && err.name === 'AbortError')
10575
- || message.includes('aborted');
10629
+ || (err instanceof Error && err.name === 'AbortError');
10576
10630
  workerSelf.postMessage({
10577
10631
  type: 'fetchError',
10578
10632
  fetchId: msg.fetchId,