@gmod/cram
Version:
read CRAM files with pure Javascript
126 lines • 4.67 kB
JavaScript
/* !
* This is adapted from the xz-decompress NPM package.
* https://github.com/httptoolkit/xz-decompress/ License MIT
*
* That codebase in turn had these citations
* Based on xzwasm (c) Steve Sanderson. License: MIT - https://github.com/SteveSanderson/xzwasm
* Contains xz-embedded by Lasse Collin and Igor Pavlov. License: Public domain - https://tukaani.org/xz/embedded.html
* and walloc (c) 2020 Igalia, S.L. License: MIT - https://github.com/wingo/walloc
*/
import { xzdecompressWasm as wasmBase64 } from "./wasm.js";
const XZ_OK = 0;
const XZ_STREAM_END = 1;
class XzContext {
exports;
memory;
ptr;
bufSize;
inStart;
outStart;
mem8;
mem32;
constructor(moduleInstance) {
this.exports = moduleInstance.exports;
this.memory = this.exports.memory;
this.ptr = this.exports.create_context();
this._refresh();
this.bufSize = this.mem32[0];
this.inStart = this.mem32[1] - this.ptr;
this.outStart = this.mem32[4] - this.ptr;
}
supplyInput(sourceDataUint8Array) {
this._refresh();
const inBuffer = this.mem8.subarray(this.inStart, this.inStart + this.bufSize);
inBuffer.set(sourceDataUint8Array, 0);
this.exports.supply_input(this.ptr, sourceDataUint8Array.byteLength);
}
getNextOutput() {
const result = this.exports.get_next_output(this.ptr);
this._refresh();
if (result !== XZ_OK && result !== XZ_STREAM_END) {
throw new Error(`get_next_output failed with error code ${result}`);
}
const outChunk = this.mem8.slice(this.outStart, this.outStart + /* outPos */ this.mem32[5]);
return { outChunk, finished: result === XZ_STREAM_END };
}
dispose() {
this.exports.destroy_context(this.ptr);
}
_refresh() {
const currentBuffer = this.memory.buffer;
if (currentBuffer !== this.mem8?.buffer) {
this.mem8 = new Uint8Array(currentBuffer, this.ptr);
this.mem32 = new Uint32Array(currentBuffer, this.ptr);
}
}
}
let _moduleInstancePromise;
let _moduleInstance;
const _emptyInput = new Uint8Array(0);
async function _getModuleInstance() {
const base64Wasm = wasmBase64.replace('data:application/wasm;base64,', '');
const binaryString = atob(base64Wasm);
const len = binaryString.length;
const wasmBytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
wasmBytes[i] = binaryString.charCodeAt(i);
}
const module = await WebAssembly.instantiate(wasmBytes.buffer, {});
_moduleInstance = module.instance;
}
export async function xzDecompress(input) {
if (!_moduleInstance) {
await (_moduleInstancePromise ||
(_moduleInstancePromise = _getModuleInstance()));
}
const context = new XzContext(_moduleInstance);
const chunks = [];
let offset = 0;
let eofSignaled = false;
try {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
// Check if WASM needs more input (inPos === inSize)
if (context.mem32[2] === context.mem32[3]) {
if (offset < input.length) {
const chunkSize = Math.min(context.bufSize, input.length - offset);
context.supplyInput(input.subarray(offset, offset + chunkSize));
offset += chunkSize;
}
else if (!eofSignaled) {
// Signal EOF by supplying empty input once
context.supplyInput(_emptyInput);
eofSignaled = true;
}
else {
// Stuck in a loop - WASM needs more input but we're at EOF
throw new Error('XZ decompression error: unexpected end of input');
}
}
const result = context.getNextOutput();
if (result.outChunk.length > 0) {
chunks.push(result.outChunk);
}
// Reset output buffer position
context.mem32[5] = 0;
if (result.finished) {
break;
}
}
if (chunks.length === 1) {
return chunks[0];
}
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const output = new Uint8Array(totalLength);
let position = 0;
for (const chunk of chunks) {
output.set(chunk, position);
position += chunk.length;
}
return output;
}
finally {
context.dispose();
}
}
//# sourceMappingURL=xz-decompress.js.map