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.
- package/dist/loom-react.esm.js +3877 -3173
- package/dist/loom-react.esm.min.js +1 -1
- package/dist/loom-react.esm.min.js.map +1 -1
- package/dist/loom-worker.js +191 -137
- package/dist/loom-worker.min.js +1 -1
- package/dist/loom-worker.min.js.map +1 -1
- package/dist/loom.esm.js +4163 -3412
- package/dist/loom.esm.min.js +1 -1
- package/dist/loom.esm.min.js.map +1 -1
- package/dist/loom.js +4174 -3415
- package/dist/loom.min.js +1 -1
- package/dist/loom.min.js.map +1 -1
- package/dist/tsconfig.src.tsbuildinfo +1 -1
- package/dist/types/browser/dom/browserExport.d.ts +32 -0
- package/dist/types/browser/dom/contextMenu.d.ts +64 -0
- package/dist/types/browser/dom/contextMenuManager.d.ts +49 -0
- package/dist/types/browser/dom/defaultProviders.d.ts +23 -0
- package/dist/types/browser/dom/genomeBrowser.d.ts +171 -0
- package/dist/types/browser/dom/pointerEventManager.d.ts +109 -0
- package/dist/types/browser/dom/roiOverlayManager.d.ts +38 -0
- package/dist/types/browser/dom/svgFeatureOverlay.d.ts +27 -0
- package/dist/types/browser/headless/errors.d.ts +10 -0
- package/dist/types/browser/headless/headlessGenomeBrowser.d.ts +391 -0
- package/dist/types/browser/headless/roiManager.d.ts +47 -0
- package/dist/types/browser/headless/session.d.ts +45 -0
- package/dist/types/browser/headless/trackDataManager.d.ts +48 -0
- package/dist/types/browser/headless/trackFactories.d.ts +145 -0
- package/dist/types/browser/index.d.ts +24 -0
- package/dist/types/browserExport.d.ts +32 -0
- package/dist/types/commandDispatcher.d.ts +1 -1
- package/dist/types/contextMenuManager.d.ts +49 -0
- package/dist/types/data/searchService.d.ts +24 -0
- package/dist/types/dataSourceWorkerProvider.d.ts +3 -3
- package/dist/types/dataSources/bigWigDataSource.d.ts +2 -2
- package/dist/types/dataSources/config.d.ts +40 -0
- package/dist/types/dataSources/configureDataSource.d.ts +17 -0
- package/dist/types/dataSources/createDataSource.d.ts +14 -0
- package/dist/types/dataSources/geneDataSource.d.ts +4 -3
- package/dist/types/dataSources/gtxDataSource.d.ts +2 -2
- package/dist/types/dataSources/memoryDataSource.d.ts +2 -2
- package/dist/types/dataSources/sequenceDataSource.d.ts +4 -1
- package/dist/types/dataSources/textFeatureSource.d.ts +4 -2
- package/dist/types/dataSources/wholeGenomeUtils.d.ts +37 -0
- package/dist/types/defaultProviders.d.ts +23 -0
- package/dist/types/errors.d.ts +10 -0
- package/dist/types/formats/featureParser.d.ts +1 -1
- package/dist/types/genomeBrowser.d.ts +18 -91
- package/dist/types/headlessGenomeBrowser.d.ts +88 -225
- package/dist/types/index.d.ts +21 -14
- package/dist/types/io/binaryParser.d.ts +0 -1
- package/dist/types/logger.d.ts +20 -0
- package/dist/types/pointerEventManager.d.ts +109 -0
- package/dist/types/react/GenomeBrowserContext.d.ts +1 -1
- package/dist/types/react/LoomBrowser.d.ts +4 -4
- package/dist/types/react/hooks/useBrowserEvent.d.ts +1 -1
- package/dist/types/react/hooks/useGenomeBrowser.d.ts +1 -1
- package/dist/types/react/hooks/useTrackManager.d.ts +1 -1
- package/dist/types/react/ui/ChromosomeSelect.d.ts +1 -1
- package/dist/types/react/ui/ExportControls.d.ts +1 -1
- package/dist/types/react/ui/LocusInput.d.ts +1 -1
- package/dist/types/react/ui/Navbar.d.ts +1 -1
- package/dist/types/react/ui/WindowSize.d.ts +1 -1
- package/dist/types/react/ui/ZoomControls.d.ts +1 -1
- package/dist/types/remoteProtocol.d.ts +1 -1
- package/dist/types/roiManager.d.ts +47 -0
- package/dist/types/roiOverlayManager.d.ts +38 -0
- package/dist/types/stateProjection.d.ts +1 -1
- package/dist/types/trackDataManager.d.ts +48 -0
- package/dist/types/trackFactories.d.ts +140 -0
- package/dist/types/trackSelector.d.ts +1 -1
- package/dist/types/tracks/annotation/annotationTrackCanvas.d.ts +0 -1
- package/dist/types/tracks/annotation/config.d.ts +61 -0
- package/dist/types/tracks/baseTrackCanvas.d.ts +10 -0
- package/dist/types/tracks/configDiff.d.ts +14 -0
- package/dist/types/tracks/interaction/config.d.ts +40 -0
- package/dist/types/tracks/ruler/config.d.ts +24 -0
- package/dist/types/tracks/sequence/config.d.ts +56 -0
- package/dist/types/tracks/sequence/sequenceTrackCanvas.d.ts +3 -0
- package/dist/types/tracks/wig/config.d.ts +77 -0
- package/dist/types/types/igvCompat.d.ts +36 -0
- package/dist/types/types.d.ts +48 -284
- package/dist/types/ui/components/LoomBrowserShell.d.ts +2 -2
- package/dist/types/ui/components/LoomChromosomeSelect.d.ts +1 -1
- package/dist/types/ui/components/LoomContextMenu.d.ts +1 -0
- package/dist/types/ui/components/LoomExportControls.d.ts +1 -1
- package/dist/types/ui/components/LoomLocusInput.d.ts +1 -1
- package/dist/types/ui/components/LoomNavbar.d.ts +1 -1
- package/dist/types/ui/components/LoomWindowSize.d.ts +1 -1
- package/dist/types/ui/components/LoomZoomControls.d.ts +1 -1
- package/dist/types/undoManager.d.ts +49 -0
- package/dist/types/worker/dataSourceRegistry.d.ts +2 -2
- package/dist/types/worker/serializedError.d.ts +16 -0
- package/dist/types/worker/taskTimeout.d.ts +21 -0
- package/dist/types/worker/webWorkerPool.d.ts +5 -2
- package/dist/types/worker/webWorkerProvider.d.ts +3 -0
- package/dist/types/workerDataSource.d.ts +2 -2
- package/dist/types/workerProvider.d.ts +2 -2
- package/package.json +1 -1
package/dist/loom-worker.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
5027
|
-
const
|
|
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
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
)
|
|
5052
|
-
const
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
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 >
|
|
5075
|
-
return reservoirSample(wgFeatures,
|
|
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
|
-
|
|
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
|
|
10403
|
-
for (const
|
|
10404
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
10548
|
+
* Structured logger for the Loom genome browser.
|
|
10486
10549
|
*
|
|
10487
|
-
*
|
|
10488
|
-
*
|
|
10489
|
-
*
|
|
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
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10571
|
-
//
|
|
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,
|