@gmod/nclist
Version:
Read features from JBrowse 1 format nested containment list JSON
82 lines (74 loc) • 2.64 kB
text/typescript
//@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]]
}
}
}