UNPKG

@gmod/nclist

Version:

Read features from JBrowse 1 format nested containment list JSON

82 lines (74 loc) 2.64 kB
//@ts-nocheck import QuickLRU from 'quick-lru' import AbortablePromiseCache from '@gmod/abortable-promise-cache' import { newURL, readJSON } from './util.ts' /** * For a JSON array that gets too large to load in one go, this class * helps break it up into chunks and provides an * async API for using the information in the array. */ export default class LazyArray { constructor( { urlTemplate, chunkSize, length, cacheSize = 100, readFile }, baseUrl, ) { this.urlTemplate = urlTemplate this.chunkSize = chunkSize this.length = length this.baseUrl = baseUrl === undefined ? '' : baseUrl this.readFile = readFile if (!readFile) { throw new Error('must provide readFile callback') } this.chunkCache = new AbortablePromiseCache({ cache: new QuickLRU({ maxSize: cacheSize }), fill: this.getChunk.bind(this), }) } /** * call the callback on one element of the array * @param i index * @param callback callback, gets called with (i, value, param) * @param param (optional) callback will get this as its last parameter */ index(i, callback, param) { this.range(i, i, callback, undefined, param) } /** * async generator for the elements in the range [start,end] * * @param start index of first element to call the callback on * @param end index of last element to call the callback on */ async *range(start, end) { start = Math.max(0, start) end = Math.min(end, this.length - 1) const firstChunk = Math.floor(start / this.chunkSize) const lastChunk = Math.floor(end / this.chunkSize) const chunkreadFiles = [] for (let chunk = firstChunk; chunk <= lastChunk; chunk += 1) { chunkreadFiles.push(this.chunkCache.get(chunk, chunk)) } for (const elt of chunkreadFiles) { const [chunkNumber, chunkData] = await elt yield* this.filterChunkData(start, end, chunkNumber, chunkData) } } async getChunk(chunkNumber) { let url = this.urlTemplate.replaceAll(/\{Chunk\}/gi, chunkNumber) if (this.baseUrl) { url = newURL(url, this.baseUrl) } const data = await readJSON(url, this.readFile) return [chunkNumber, data] } *filterChunkData(queryStart, queryEnd, chunkNumber, chunkData) { // index (in the overall lazy array) of the first position in this chunk const firstIndex = chunkNumber * this.chunkSize const chunkStart = Math.max(0, queryStart - firstIndex) const chunkEnd = Math.min(queryEnd - firstIndex, this.chunkSize - 1) for (let i = chunkStart; i <= chunkEnd; i += 1) { yield [i + firstIndex, chunkData[i]] } } }