loom-browser 0.0.13 → 0.0.15

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.
@@ -3221,7 +3221,7 @@ function groupBlocks(blocks) {
3221
3221
  return blockGroups;
3222
3222
  }
3223
3223
 
3224
- const decoder$1 = new TextDecoder('utf8');
3224
+ const decoder$2 = new TextDecoder('utf8');
3225
3225
  const CIR_TREE_MAGIC = 0x2468ace0;
3226
3226
  function coordFilter(s1, e1, s2, e2) {
3227
3227
  return s1 < e2 && e1 >= s2;
@@ -3279,7 +3279,7 @@ function parseBigBedBlock(data, startOffset, offset, request) {
3279
3279
  }
3280
3280
  }
3281
3281
  const b = data.subarray(currOffset, i);
3282
- const rest = decoder$1.decode(b);
3282
+ const rest = decoder$2.decode(b);
3283
3283
  currOffset = i + 1;
3284
3284
  if (!request ||
3285
3285
  (chromId === request.chrId &&
@@ -3805,7 +3805,7 @@ class BlockView {
3805
3805
 
3806
3806
  const BIG_WIG_MAGIC = -2003829722;
3807
3807
  const BIG_BED_MAGIC = -2021002517;
3808
- const decoder = new TextDecoder('utf8');
3808
+ const decoder$1 = new TextDecoder('utf8');
3809
3809
  function getDataView(buffer) {
3810
3810
  return new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
3811
3811
  }
@@ -3953,7 +3953,7 @@ class BBI {
3953
3953
  fileType,
3954
3954
  version,
3955
3955
  autoSql: asOffset
3956
- ? decoder.decode(b.subarray(asOffset, b.indexOf(0, asOffset)))
3956
+ ? decoder$1.decode(b.subarray(asOffset, b.indexOf(0, asOffset)))
3957
3957
  : '',
3958
3958
  };
3959
3959
  }
@@ -3982,7 +3982,7 @@ class BBI {
3982
3982
  const effectiveKeyEnd = keyEnd !== -1 && keyEnd < offset + keySize
3983
3983
  ? keyEnd
3984
3984
  : offset + keySize;
3985
- const key = decoder.decode(b.subarray(offset, effectiveKeyEnd));
3985
+ const key = decoder$1.decode(b.subarray(offset, effectiveKeyEnd));
3986
3986
  offset += keySize;
3987
3987
  const refId = dataView.getUint32(offset, true);
3988
3988
  offset += 4;
@@ -4068,7 +4068,214 @@ class BigWig extends BBI {
4068
4068
  }
4069
4069
  }
4070
4070
 
4071
- new TextDecoder('utf8');
4071
+ const decoder = new TextDecoder('utf8');
4072
+ function getTabField(str, fieldIndex) {
4073
+ if (fieldIndex < 0) {
4074
+ return undefined;
4075
+ }
4076
+ let start = 0;
4077
+ for (let i = 0; i < fieldIndex; i++) {
4078
+ start = str.indexOf('\t', start);
4079
+ if (start === -1) {
4080
+ return undefined;
4081
+ }
4082
+ start++;
4083
+ }
4084
+ const end = str.indexOf('\t', start);
4085
+ return end === -1 ? str.slice(start) : str.slice(start, end);
4086
+ }
4087
+ // Parses a null-terminated string key from a B+ tree node
4088
+ function parseKey(buffer, offset, keySize) {
4089
+ const keyEnd = buffer.indexOf(0, offset);
4090
+ const effectiveKeyEnd = keyEnd !== -1 && keyEnd < offset + keySize ? keyEnd : offset + keySize;
4091
+ return decoder.decode(buffer.subarray(offset, effectiveKeyEnd));
4092
+ }
4093
+ // Recursively traverses a B+ tree to search for a specific name in the BigBed extraIndex
4094
+ // B+ trees are balanced tree structures optimized for disk-based searches
4095
+ async function readBPlusTreeNode(bbi, nodeOffset, blockSize, keySize, valSize, name, field, opts) {
4096
+ const len = 4 + blockSize * (keySize + valSize);
4097
+ const buffer = await bbi.read(len, nodeOffset, opts);
4098
+ const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
4099
+ const nodeType = dataView.getInt8(0);
4100
+ const cnt = dataView.getInt16(2, true);
4101
+ let offset = 4;
4102
+ // Non-leaf node (nodeType === 0): contains keys and child node pointers for navigation
4103
+ if (nodeType === 0) {
4104
+ const leafkeys = [];
4105
+ for (let i = 0; i < cnt; i++) {
4106
+ const key = parseKey(buffer, offset, keySize);
4107
+ offset += keySize;
4108
+ const dataOffset = Number(dataView.getBigUint64(offset, true));
4109
+ offset += 8;
4110
+ leafkeys.push({
4111
+ key,
4112
+ offset: dataOffset,
4113
+ });
4114
+ }
4115
+ // Binary search to find the appropriate child node
4116
+ let left = 0;
4117
+ let right = leafkeys.length - 1;
4118
+ let targetIndex = leafkeys.length - 1;
4119
+ while (left <= right) {
4120
+ const mid = Math.floor((left + right) / 2);
4121
+ const cmp = name.localeCompare(leafkeys[mid].key);
4122
+ if (cmp < 0) {
4123
+ targetIndex = mid - 1;
4124
+ right = mid - 1;
4125
+ }
4126
+ else {
4127
+ left = mid + 1;
4128
+ }
4129
+ }
4130
+ const childOffset = targetIndex >= 0 ? leafkeys[targetIndex].offset : leafkeys[0].offset;
4131
+ return readBPlusTreeNode(bbi, childOffset, blockSize, keySize, valSize, name, field, opts);
4132
+ }
4133
+ else if (nodeType === 1) {
4134
+ // Leaf node (nodeType === 1): contains actual key-value data
4135
+ const keys = [];
4136
+ for (let i = 0; i < cnt; i++) {
4137
+ const key = parseKey(buffer, offset, keySize);
4138
+ offset += keySize;
4139
+ const dataOffset = Number(dataView.getBigUint64(offset, true));
4140
+ offset += 8;
4141
+ const length = dataView.getUint32(offset, true);
4142
+ offset += 4;
4143
+ offset += 4; // skip reserved
4144
+ keys.push({
4145
+ key,
4146
+ offset: dataOffset,
4147
+ length,
4148
+ });
4149
+ }
4150
+ // Binary search for exact key match in sorted leaf node
4151
+ let left = 0;
4152
+ let right = keys.length - 1;
4153
+ while (left <= right) {
4154
+ const mid = Math.floor((left + right) / 2);
4155
+ const cmp = name.localeCompare(keys[mid].key);
4156
+ if (cmp === 0) {
4157
+ return { ...keys[mid], field };
4158
+ }
4159
+ else if (cmp < 0) {
4160
+ right = mid - 1;
4161
+ }
4162
+ else {
4163
+ left = mid + 1;
4164
+ }
4165
+ }
4166
+ return undefined;
4167
+ }
4168
+ }
4169
+ class BigBed extends BBI {
4170
+ readIndicesCache = new AbortablePromiseCache({
4171
+ cache: new QuickLRU({ maxSize: 1 }),
4172
+ fill: (args, signal) => this._readIndices({ ...args, signal }),
4173
+ });
4174
+ readIndices(opts = {}) {
4175
+ const { signal, ...rest } = opts;
4176
+ return this.readIndicesCache.get(JSON.stringify(rest), opts, signal);
4177
+ }
4178
+ /*
4179
+ * retrieve unzoomed view for any scale
4180
+ */
4181
+ async getView(_scale, opts) {
4182
+ return this.getUnzoomedView(opts);
4183
+ }
4184
+ /*
4185
+ * parse the bigbed extraIndex fields
4186
+ *
4187
+ *
4188
+ * @return a Promise for an array of Index data structure since there can be
4189
+ * multiple extraIndexes in a bigbed, see bedToBigBed documentation
4190
+ */
4191
+ async _readIndices(opts) {
4192
+ const { extHeaderOffset } = await this.getHeader(opts);
4193
+ const b = await this.bbi.read(64, extHeaderOffset);
4194
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length);
4195
+ const count = dataView.getUint16(2, true);
4196
+ const dataOffset = Number(dataView.getBigUint64(4, true));
4197
+ // no extra index is defined if count==0
4198
+ if (count === 0) {
4199
+ return [];
4200
+ }
4201
+ const blocklen = 20;
4202
+ const len = blocklen * count;
4203
+ const buffer = await this.bbi.read(len, dataOffset);
4204
+ const indices = [];
4205
+ for (let i = 0; i < count; i += 1) {
4206
+ const b = buffer.subarray(i * blocklen);
4207
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length);
4208
+ const type = dataView.getInt16(0, true);
4209
+ const fieldcount = dataView.getInt16(2, true);
4210
+ const dataOffset = Number(dataView.getBigUint64(4, true));
4211
+ const field = dataView.getInt16(16, true);
4212
+ indices.push({
4213
+ type,
4214
+ fieldcount,
4215
+ offset: dataOffset,
4216
+ field,
4217
+ });
4218
+ }
4219
+ return indices;
4220
+ }
4221
+ /*
4222
+ * perform a search in the bigbed extraIndex to find which blocks in the
4223
+ * bigbed data to look for the actual feature data
4224
+ *
4225
+ * @param name - the name to search for
4226
+ *
4227
+ * @param opts - a SearchOptions argument with optional signal
4228
+ *
4229
+ * @return a Promise for an array of bigbed block Loc entries
4230
+ */
4231
+ async searchExtraIndexBlocks(name, opts = {}) {
4232
+ const indices = await this.readIndices(opts);
4233
+ if (indices.length === 0) {
4234
+ return [];
4235
+ }
4236
+ const locs = indices.map(async (index) => {
4237
+ const { offset: offset2, field } = index;
4238
+ const b = await this.bbi.read(32, offset2, opts);
4239
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length);
4240
+ const blockSize = dataView.getInt32(4, true);
4241
+ const keySize = dataView.getInt32(8, true);
4242
+ const valSize = dataView.getInt32(12, true);
4243
+ return readBPlusTreeNode(this.bbi, offset2 + 32, blockSize, keySize, valSize, name, field, opts);
4244
+ });
4245
+ const results = await Promise.all(locs);
4246
+ return results.filter((l) => l !== undefined);
4247
+ }
4248
+ /*
4249
+ * retrieve the features from the bigbed data that were found through the
4250
+ * lookup of the extraIndex note that there can be multiple extraIndex, see
4251
+ * the BigBed specification and the -extraIndex argument to bedToBigBed
4252
+ *
4253
+ * @param name - the name to search for
4254
+ *
4255
+ * @param opts - options object with optional AbortSignal
4256
+ *
4257
+ * @return array of Feature
4258
+ */
4259
+ async searchExtraIndex(name, opts = {}) {
4260
+ const blocks = await this.searchExtraIndexBlocks(name, opts);
4261
+ if (blocks.length === 0) {
4262
+ return [];
4263
+ }
4264
+ const view = await this.getUnzoomedView(opts);
4265
+ const results = await Promise.all(blocks.map(async (block) => {
4266
+ const features = await view.readFeatures([block], opts);
4267
+ return features.map(f => ({ ...f, field: block.field }));
4268
+ }));
4269
+ // field offset is adjusted by -3 to account for chrom, chromStart, chromEnd columns
4270
+ return results.flat().filter(f => {
4271
+ if (!f.rest) {
4272
+ return false;
4273
+ }
4274
+ const fieldIndex = (f.field || 0) - 3;
4275
+ return getTabField(f.rest, fieldIndex) === name;
4276
+ });
4277
+ }
4278
+ }
4072
4279
 
4073
4280
  /**
4074
4281
  * Google Cloud URL utilities — pure functions for detecting, parsing, and
@@ -4350,14 +4557,14 @@ class BinaryParser {
4350
4557
  // ─── Internal helpers ─────────────────────────────────────────────────────────
4351
4558
  // Cache BigWig instances by URL to reuse parsed headers and trees.
4352
4559
  // Auth-aware readers share the cache key (URL) — the fetchImpl is bound at creation.
4353
- const readerCache$2 = new Map();
4560
+ const readerCache$3 = new Map();
4354
4561
  function getGmodReader(url, fetchImpl) {
4355
4562
  const resolvedUrl = normalizeGoogleURL(url);
4356
- let reader = readerCache$2.get(resolvedUrl);
4563
+ let reader = readerCache$3.get(resolvedUrl);
4357
4564
  if (!reader) {
4358
4565
  const fileOpts = fetchImpl ? { fetch: fetchImpl } : undefined;
4359
4566
  reader = new BigWig({ filehandle: new RemoteFile(resolvedUrl, fileOpts) });
4360
- readerCache$2.set(resolvedUrl, reader);
4567
+ readerCache$3.set(resolvedUrl, reader);
4361
4568
  }
4362
4569
  return reader;
4363
4570
  }
@@ -4478,7 +4685,7 @@ async function fetchBigWigFeatures(url, locus, options = {}) {
4478
4685
  // Evict the cached reader so the next fetch gets a fresh one.
4479
4686
  // @gmod/bbi caches the header promise; an aborted parse poisons it permanently.
4480
4687
  if (err instanceof Error && (err.name === 'AbortError' || ((_b = err.message) === null || _b === void 0 ? void 0 : _b.includes('aborted')))) {
4481
- readerCache$2.delete(url);
4688
+ readerCache$3.delete(url);
4482
4689
  }
4483
4690
  throw err;
4484
4691
  }
@@ -4614,6 +4821,102 @@ class BigWigDataSource {
4614
4821
  }
4615
4822
  }
4616
4823
 
4824
+ /**
4825
+ * BigBed data source — fetches BED features from BigBed binary files.
4826
+ *
4827
+ * Uses @gmod/bbi's BigBed class for binary parsing and range queries.
4828
+ * Returns BedFeature[] suitable for annotation track rendering.
4829
+ *
4830
+ * Mirrors igv.js BWSource behavior for bigbed format:
4831
+ * - Features are decoded from BED rest fields via decodeBed()
4832
+ * - No zoom-level summarization (unlike BigWig)
4833
+ * - Feature density estimation from header dataCount
4834
+ *
4835
+ * Layer 1 (Data + Layout): no DOM.
4836
+ */
4837
+ // Cache BigBed instances by URL to reuse parsed headers and index trees.
4838
+ const readerCache$2 = new Map();
4839
+ function getReader$2(url, fetchImpl) {
4840
+ const resolvedUrl = normalizeGoogleURL(url);
4841
+ let reader = readerCache$2.get(resolvedUrl);
4842
+ if (!reader) {
4843
+ const fileOpts = fetchImpl ? { fetch: fetchImpl } : undefined;
4844
+ reader = new BigBed({ filehandle: new RemoteFile(resolvedUrl, fileOpts) });
4845
+ readerCache$2.set(resolvedUrl, reader);
4846
+ }
4847
+ return reader;
4848
+ }
4849
+ class BigBedDataSource {
4850
+ constructor(url, fetchImpl) {
4851
+ this.url = url;
4852
+ this.fetchImpl = fetchImpl;
4853
+ this.bb = getReader$2(url, fetchImpl);
4854
+ }
4855
+ /** Set a chromosome name resolver for alias resolution (e.g., "1" → "chr1"). */
4856
+ setChromNameResolver(resolver) {
4857
+ this._resolveChromName = resolver;
4858
+ }
4859
+ async fetch(locus, _bpPerPixel, signal) {
4860
+ var _a;
4861
+ const chr = this._resolveChromName
4862
+ ? this._resolveChromName(locus.chr)
4863
+ : locus.chr;
4864
+ try {
4865
+ const rawFeatures = await this.bb.getFeatures(chr, locus.start, locus.end, { signal });
4866
+ const result = [];
4867
+ for (const f of rawFeatures) {
4868
+ const parsed = parseBigBedFeature(chr, f.start, f.end, f.rest);
4869
+ if (parsed)
4870
+ result.push(parsed);
4871
+ }
4872
+ return result;
4873
+ }
4874
+ catch (err) {
4875
+ // Evict poisoned reader on abort (same pattern as BigWig)
4876
+ if (err instanceof Error && (err.name === 'AbortError' || ((_a = err.message) === null || _a === void 0 ? void 0 : _a.includes('aborted')))) {
4877
+ readerCache$2.delete(normalizeGoogleURL(this.url));
4878
+ }
4879
+ throw err;
4880
+ }
4881
+ }
4882
+ /**
4883
+ * Search for a feature by name via BigBed extra index.
4884
+ * Returns matching features, or empty array if not found or not indexed.
4885
+ */
4886
+ async search(name) {
4887
+ const header = await this.bb.getHeader();
4888
+ const results = await this.bb.searchExtraIndex(name);
4889
+ if (results.length === 0)
4890
+ return [];
4891
+ const refsByNumber = header.refsByNumber;
4892
+ const features = [];
4893
+ for (const r of results) {
4894
+ const chromId = r.chromId;
4895
+ if (chromId == null || !(refsByNumber === null || refsByNumber === void 0 ? void 0 : refsByNumber[chromId]))
4896
+ continue;
4897
+ const chr = refsByNumber[chromId].name;
4898
+ const parsed = parseBigBedFeature(chr, r.start, r.end, r.rest);
4899
+ if (parsed)
4900
+ features.push(parsed);
4901
+ }
4902
+ return features;
4903
+ }
4904
+ dispose() {
4905
+ readerCache$2.delete(normalizeGoogleURL(this.url));
4906
+ }
4907
+ }
4908
+ /**
4909
+ * Parse a BigBed feature's rest field into a BedFeature.
4910
+ * Reconstructs BED tokens from chr/start/end + tab-delimited rest, then uses decodeBed().
4911
+ */
4912
+ function parseBigBedFeature(chr, start, end, rest) {
4913
+ const tokens = [chr, String(start), String(end)];
4914
+ if (rest) {
4915
+ tokens.push(...rest.split('\t'));
4916
+ }
4917
+ return decodeBed(tokens);
4918
+ }
4919
+
4617
4920
  /**
4618
4921
  * HTTP range request reader using native fetch.
4619
4922
  * Replaces igvxhr + BufferedReader for BigWig file reading.
@@ -11090,6 +11393,10 @@ function createDataSource(config, fetchImplOverride) {
11090
11393
  const fi = fetchImplOverride !== null && fetchImplOverride !== void 0 ? fetchImplOverride : buildFetchImpl(config);
11091
11394
  return new BigWigDataSource(config.url, (_a = config.windowFunction) !== null && _a !== void 0 ? _a : 'mean', fi);
11092
11395
  }
11396
+ case 'bigbed': {
11397
+ const fi = fetchImplOverride !== null && fetchImplOverride !== void 0 ? fetchImplOverride : buildFetchImpl(config);
11398
+ return new BigBedDataSource(config.url, fi);
11399
+ }
11093
11400
  case 'gtx': {
11094
11401
  const fi = fetchImplOverride !== null && fetchImplOverride !== void 0 ? fetchImplOverride : buildFetchImpl(config);
11095
11402
  return new GtxDataSource(config.url, config.experimentId, (_b = config.windowFunction) !== null && _b !== void 0 ? _b : 'mean', fi);