loom-browser 0.0.14 → 0.0.16

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.
@@ -9877,50 +9877,95 @@ function createGenomeSync(config) {
9877
9877
  return new GenomeImpl(id, config.chromSizes, { name, sequence, cytobands: config.cytobands, nameSet: config.nameSet });
9878
9878
  }
9879
9879
  // ─── Sequence resolution ────��─────────────────────────────────���──────────────
9880
+ /**
9881
+ * Wrap a primary SequenceProvider with a fallback.
9882
+ *
9883
+ * On each request, tries `primary` first. If it throws (and the signal was
9884
+ * not aborted), tries `fallback`. If both fail, the primary error is thrown
9885
+ * so the caller sees the error from their configured source.
9886
+ *
9887
+ * The caching layer (createCachedSequence) wraps the combined provider,
9888
+ * so successful fallback results are cached normally — subsequent requests
9889
+ * for the same region won't re-attempt the primary.
9890
+ */
9891
+ function createFallbackProvider(primary, fallback) {
9892
+ return async (locus, signal) => {
9893
+ try {
9894
+ return await primary(locus, signal);
9895
+ }
9896
+ catch (primaryErr) {
9897
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted)
9898
+ throw primaryErr;
9899
+ console.warn('[loom] Primary sequence source failed, trying fallback:', primaryErr.message);
9900
+ try {
9901
+ return await fallback(locus, signal);
9902
+ }
9903
+ catch (_a) {
9904
+ // Both failed — throw the primary error (more relevant to the user's config)
9905
+ throw primaryErr;
9906
+ }
9907
+ }
9908
+ };
9909
+ }
9880
9910
  /**
9881
9911
  * Resolve a sequence provider from config.
9882
9912
  *
9883
- * Exactly one sequence source must be set. Throws if multiple are provided.
9884
- * Sources: sequenceProvider, twoBitURL, fastaURL, ucscGenome.
9885
- * When none is set, falls back to UCSC API via `id` if available.
9913
+ * If a custom `sequenceProvider` is set, it is used directly. Otherwise,
9914
+ * all available sources (twoBitURL, fastaURL, ucscGenome/id) are chained
9915
+ * as a prioritized fallback list:
9916
+ *
9917
+ * 1. twoBitURL — most efficient (binary range requests)
9918
+ * 2. fastaURL — reliable CDN fallback (igv.org hosts FASTA for common genomes)
9919
+ * 3. UCSC API — last resort (api.genome.ucsc.edu REST endpoint)
9920
+ *
9921
+ * This mirrors igv.js genomes3.json which provides both twoBitURL and fastaURL
9922
+ * for common genomes. On networks where one server is unreachable (e.g.,
9923
+ * hgdownload.soe.ucsc.edu blocked but igv.org reachable), the chain
9924
+ * automatically falls through to the next available source.
9886
9925
  */
9887
9926
  function resolveSequenceProvider(config) {
9888
9927
  var _a;
9889
- const sources = [
9890
- config.sequenceProvider && 'sequenceProvider',
9891
- config.twoBitURL && 'twoBitURL',
9892
- config.fastaURL && 'fastaURL',
9893
- config.ucscGenome && 'ucscGenome',
9894
- ].filter(Boolean);
9895
- if (sources.length > 1) {
9896
- throw new Error(`GenomeConfig has multiple sequence sources: ${sources.join(', ')}. ` +
9897
- 'Set exactly one of: sequenceProvider, twoBitURL, fastaURL, or ucscGenome.');
9898
- }
9928
+ // Custom provider — use directly, no chaining
9899
9929
  if (config.sequenceProvider) {
9900
9930
  return config.sequenceProvider;
9901
9931
  }
9932
+ // Build prioritized provider list from available config fields
9933
+ const providers = [];
9902
9934
  if (config.twoBitURL) {
9903
- return createCachedSequence(createTwoBitSequenceProvider(config.twoBitURL, config.fetchImpl));
9935
+ providers.push(createTwoBitSequenceProvider(config.twoBitURL, config.fetchImpl));
9904
9936
  }
9905
9937
  if (config.fastaURL) {
9906
- return createCachedSequence(createFastaSequenceProvider(config.fastaURL, config.indexURL, config.compressedIndexURL, config.fetchImpl));
9938
+ providers.push(createFastaSequenceProvider(config.fastaURL, config.indexURL, config.compressedIndexURL, config.fetchImpl));
9907
9939
  }
9908
- // UCSC API — explicit ucscGenome, or implicit via id
9909
9940
  const ucscGenome = (_a = config.ucscGenome) !== null && _a !== void 0 ? _a : config.id;
9910
9941
  if (ucscGenome) {
9911
- return createCachedSequence((locus, signal) => fetchSequence(locus, { genome: ucscGenome }, signal));
9942
+ providers.push((locus, signal) => fetchSequence(locus, { genome: ucscGenome }, signal));
9912
9943
  }
9913
- return undefined;
9944
+ if (providers.length === 0)
9945
+ return undefined;
9946
+ if (providers.length === 1)
9947
+ return createCachedSequence(providers[0]);
9948
+ // Chain all providers: try each in order, fall back on failure
9949
+ const chained = providers.reduceRight((fallback, primary) => createFallbackProvider(primary, fallback));
9950
+ return createCachedSequence(chained);
9914
9951
  }
9915
9952
  // ─── Pre-built singletons ��─────────��─────────────────────────────────────────
9916
9953
  /**
9917
- * Pre-built hg38 genome with UCSC sequence API.
9954
+ * Pre-built hg38 genome with triple-fallback sequence chain.
9918
9955
  *
9919
9956
  * This is the default genome used by HeadlessGenomeBrowser when no genome
9920
- * is specified. Equivalent to igv.js's default behavior of loading hg38.
9957
+ * is specified. Sequence sources are tried in order:
9958
+ * 1. 2-bit from hgdownload.soe.ucsc.edu (most efficient, binary range requests)
9959
+ * 2. Indexed FASTA from igv.org (reliable CDN, reachable on restricted networks)
9960
+ * 3. UCSC REST API from api.genome.ucsc.edu (last resort)
9961
+ *
9962
+ * URLs match igv.js genomes3.json configuration for hg38.
9921
9963
  */
9922
9964
  const hg38Genome = createGenomeSync({
9923
9965
  id: 'hg38',
9966
+ twoBitURL: 'https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.2bit',
9967
+ fastaURL: 'https://igv.org/genomes/data/hg38/hg38.fa',
9968
+ indexURL: 'https://igv.org/genomes/data/hg38/hg38.fa.fai',
9924
9969
  chromSizes: hg38ChromSizes,
9925
9970
  });
9926
9971
 
@@ -22102,10 +22147,12 @@ function isZoomAware(track) {
22102
22147
  * tracks point to the same URL/region.
22103
22148
  */
22104
22149
  function dataSourceCacheKey(config) {
22105
- var _a, _b, _c, _d, _e;
22150
+ var _a, _b, _c, _d, _e, _f;
22106
22151
  switch (config.type) {
22107
22152
  case 'bigwig':
22108
22153
  return `bigwig:${config.url}:${(_a = config.windowFunction) !== null && _a !== void 0 ? _a : 'mean'}`;
22154
+ case 'bigbed':
22155
+ return `bigbed:${config.url}`;
22109
22156
  case 'gtx':
22110
22157
  return `gtx:${config.url}:${config.experimentId}`;
22111
22158
  case 'ucsc':
@@ -22113,13 +22160,9 @@ function dataSourceCacheKey(config) {
22113
22160
  case 'text':
22114
22161
  return `text:${config.url}:${(_d = config.format) !== null && _d !== void 0 ? _d : ''}:${(_e = config.indexURL) !== null && _e !== void 0 ? _e : ''}`;
22115
22162
  case 'memory':
22116
- // Each memory data source is unique — no deduplication.
22117
22163
  return `memory:${Math.random()}`;
22118
- default: {
22119
- // Unknown type resolve via URL inference, then compute key from resolved config
22120
- const resolved = resolveDataSourceConfig(config);
22121
- return dataSourceCacheKey(resolved);
22122
- }
22164
+ default:
22165
+ return `unknown:${config.type}:${(_f = config.url) !== null && _f !== void 0 ? _f : Math.random()}`;
22123
22166
  }
22124
22167
  }
22125
22168