genomic-reader
Version:
A Typescript library for reading BigWig, BigBed, 2bit, and Bam files. Capable of streaming. For use in the browser or on Node.js.
209 lines • 8.28 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BufferedDataLoader = exports.FileFormatError = exports.IOError = exports.DataMissingError = exports.OutOfRangeError = exports.ErrorType = void 0;
var ErrorType;
(function (ErrorType) {
ErrorType["OUT_OF_RANGE"] = "OUT_OF_RANGE";
ErrorType["DATA_MISSING"] = "DATA_MISSING";
ErrorType["IO"] = "IO";
ErrorType["FILE_FORMAT"] = "FILE_FORMAT";
})(ErrorType = exports.ErrorType || (exports.ErrorType = {}));
;
class OutOfRangeError extends Error {
constructor(resource, start, size) {
super(`Request on ${resource} out of range. Range given: ${start}-${size || ''}`);
this.resource = resource;
this.start = start;
this.size = size;
this.errortype = ErrorType.OUT_OF_RANGE;
}
}
exports.OutOfRangeError = OutOfRangeError;
class DataMissingError extends Error {
constructor(chromosome) {
super(`Given chromosome ${chromosome} not found in file header chromosome tree`);
this.chromosome = chromosome;
this.errortype = ErrorType.DATA_MISSING;
}
}
exports.DataMissingError = DataMissingError;
class IOError extends Error {
constructor(message) {
super(message);
this.message = message;
this.errortype = ErrorType.IO;
}
}
exports.IOError = IOError;
class FileFormatError extends Error {
constructor(message) {
super(message);
this.message = message;
this.errortype = ErrorType.FILE_FORMAT;
}
}
exports.FileFormatError = FileFormatError;
class BufferedDataLoader {
constructor(dataLoader, bufferSize, streamMode = false) {
this.dataLoader = dataLoader;
this.bufferSize = bufferSize;
this.streamMode = streamMode;
}
load(start, size) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.bufferContainsData(start, size)) {
if (!this.streamMode) {
yield this.loadDataIntoBuffer(start, size);
}
else {
yield this.streamDataIntoBuffer(start, size);
}
}
return yield this.getDataFromBuffer(start, size);
});
}
loadDataIntoBuffer(start, size) {
return __awaiter(this, void 0, void 0, function* () {
let data;
try {
const loadEnd = this.bufferSize !== undefined ? Math.max(this.bufferSize, size) : undefined;
data = yield this.dataLoader.load(start, loadEnd);
}
catch (e) {
if (e instanceof OutOfRangeError) {
data = yield this.dataLoader.load(start);
}
else {
throw e;
}
}
this.buffer = {
data: data,
start: start
};
});
}
streamDataIntoBuffer(start, size) {
return __awaiter(this, void 0, void 0, function* () {
if (this.dataLoader.loadStream === undefined) {
throw Error("Stream mode enabled, but DataLoader loadStream function not defined");
}
if (this.stream !== undefined) {
this.stream.destroy();
this.stream = undefined;
}
try {
const loadEnd = this.bufferSize !== undefined ? Math.max(this.bufferSize, size) : undefined;
this.stream = yield this.dataLoader.loadStream(start, loadEnd);
}
catch (e) {
if (e instanceof OutOfRangeError) {
this.stream = yield this.dataLoader.loadStream(start);
}
else {
throw e;
}
}
const buffer = {
data: new ArrayBuffer(0),
start: start,
remainingBytes: this.bufferSize
};
this.buffer = buffer;
this.stream.on('data', (chunk) => {
buffer.data = appendBuffer(buffer.data, chunk);
if (buffer.remainingBytes !== undefined) {
buffer.remainingBytes = buffer.remainingBytes -= chunk.byteLength;
}
if (this.streamCaughtUpLock !== undefined) {
const dataEndPos = buffer.start + buffer.data.byteLength;
this.streamCaughtUpLock.updatePosition(dataEndPos);
}
});
this.stream.on('end', () => {
if (this.streamCaughtUpLock !== undefined) {
this.streamCaughtUpLock.endStream();
}
});
});
}
bufferContainsData(start, size) {
if (this.buffer === undefined)
return false;
if (this.bufferSize === undefined)
return true;
const end = start + size;
let bufferEnd = this.buffer.start + this.buffer.data.byteLength;
if (this.buffer.remainingBytes !== undefined) {
bufferEnd += this.buffer.remainingBytes;
}
return start >= this.buffer.start && end <= bufferEnd;
}
getDataFromBuffer(start, size) {
return __awaiter(this, void 0, void 0, function* () {
if (this.buffer === undefined) {
throw new Error("Invalid State. Buffer should not be empty");
}
const sliceStart = start - this.buffer.start;
const sliceEnd = sliceStart + size;
if (this.streamMode === false) {
if (size > this.buffer.data.byteLength) {
throw new IOError(`Requested ${size} bytes but only got back ${this.buffer.data.byteLength}`);
}
return this.buffer.data.slice(sliceStart, sliceEnd);
}
const currentDataEnd = this.buffer.start + this.buffer.data.byteLength;
const requiredEnd = start + size;
this.streamCaughtUpLock = new StreamCaughtUpLock(currentDataEnd, requiredEnd);
yield this.streamCaughtUpLock.waitForStream();
const response = this.buffer.data.slice(sliceStart, sliceEnd);
this.buffer.data = this.buffer.data.slice(sliceEnd, this.buffer.data.byteLength);
this.buffer.start = this.buffer.start + sliceEnd;
return response;
});
}
}
exports.BufferedDataLoader = BufferedDataLoader;
class StreamCaughtUpLock {
constructor(currentPos, caughtUpPos) {
this.currentPos = currentPos;
this.caughtUpPos = caughtUpPos;
this.promise = new Promise((resolve, reject) => {
if (this.currentPos >= this.caughtUpPos)
resolve();
this.promiseResolve = resolve;
this.promiseReject = reject;
});
}
waitForStream() {
return this.promise;
}
updatePosition(position) {
this.currentPos = position;
if (this.promiseResolve !== undefined && this.currentPos >= this.caughtUpPos) {
this.promiseResolve();
}
}
endStream() {
if (this.promiseReject !== undefined && this.currentPos < this.caughtUpPos) {
this.promiseReject("Stream ended prematurely");
}
}
}
function appendBuffer(buffer1, buffer2) {
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
;
//# sourceMappingURL=DataLoader.js.map