UNPKG

@brianpugh/tamp

Version:

Tamp compression library for JavaScript/TypeScript using WebAssembly

2 lines (1 loc) 14.4 kB
import ie from"./tamp-module.mjs";var G=0,ae=-1,le=-2,ce=-3,ue=-4,C=class extends Error{constructor(e,t,r={}){super(t),this.name="TampError",this.code=e,this.details=r}},V=class extends C{constructor(e="Provided data has more bits than expected literal bits",t={}){super(-2,e,t),this.name="ExcessBitsError"}},U=class extends C{constructor(e="Compression operation failed",t={}){super(-1,e,t),this.name="CompressionError"}},D=class extends C{constructor(e="Decompression operation failed",t={}){super(-1,e,t),this.name="DecompressionError"}},K=class{#e=null;#t=null;#r=null;async initialize(){return this.#t||(this.#t=this.#s()),await this.#t}async#s(){return this.#e=await ie(),this.#r={compressor:this.#e.ccall("tamp_compressor_sizeof","number",[],[]),decompressor:this.#e.ccall("tamp_decompressor_sizeof","number",[],[]),conf:this.#e.ccall("tamp_conf_sizeof","number",[],[])},this.#e}get module(){return this.#e}get structSizes(){return this.#r}get isInitialized(){return this.#e!==null}},H=new K;async function $(){return await H.initialize()}function x(s,e){if(s<0){let t={operation:e,code:s};switch(s){case le:throw new V(`${e}: Symbol has more bits than configured literal size`,t);case ce:throw new RangeError(`${e}: Invalid configuration parameters`);case ue:throw new RangeError(`${e}: Out-of-bounds access detected in compressed data`);case ae:default:throw e.toLowerCase().includes("compress")?new U(`${e}: Operation failed`,t):e.toLowerCase().includes("decompress")?new D(`${e}: Operation failed`,t):new C(s,`${e}: Unknown error`,t)}}return s}var R=class{constructor(e={}){if(this.options={window:10,literal:8,dictionary:null,extended:!0,lazy_matching:!1,dictionary_reset:!1,append:!1,...e},this.options.window<8||this.options.window>15)throw new RangeError(`window must be between 8 and 15, got ${this.options.window}`);if(this.options.literal<5||this.options.literal>8)throw new RangeError(`literal must be between 5 and 8, got ${this.options.literal}`);this.compressorPtr=null,this.windowPtr=null,this.module=null,this._initPromise=null}async initialize(){return this._initPromise||(this._initPromise=this._doInitialize()),this._initPromise}async _doInitialize(){this.module=await $();let e=1<<this.options.window,t=this.options.dictionary?new Uint8Array(this.options.dictionary):null;if(t&&t.length!==e)throw new RangeError(`Dictionary size (${t.length}) must match window size (${e})`);let{compressor:r,conf:n}=H.structSizes,o=r+n+e;if(this.compressorPtr=this.module._malloc(o),this.compressorPtr===0)throw new RangeError(`Failed to allocate memory for compressor (${o} bytes)`);let i=this.compressorPtr+r;this.windowPtr=i+n,t?this.module.HEAPU8.set(t,this.windowPtr):this.module.ccall("tamp_initialize_dictionary",null,["number","number","number"],[this.windowPtr,e,this.options.extended?this.options.literal:8]);try{let a=this.options.window&15|(this.options.literal&15)<<4|(this.options.dictionary?1:0)<<8|(this.options.extended?1:0)<<9|(this.options.dictionary_reset?1:0)<<10|(this.options.append?1:0)<<11|(this.options.lazy_matching?1:0)<<12;this.module.setValue(i,a,"i32");let c=this.module.ccall("tamp_compressor_init","number",["number","number","number"],[this.compressorPtr,i,this.windowPtr]);x(c,"Compressor initialization")}catch(a){throw this.module._free(this.compressorPtr),this.compressorPtr=null,this.windowPtr=null,a}}async compress(e,t={}){let r=t.onPoll;if(await this.initialize(),!this.compressorPtr)throw new Error("Compressor has been destroyed");let n=1<<20,o=[],{signal:i,pollIntervalMs:a=100,pollIntervalBytes:c=65536}=t,u=performance.now(),h=u,d=0,y=0,m=()=>{if(i!=null&&i.aborted)throw new U("Compression was aborted",{aborted:!0,reason:i.reason})},l=n+n+16,b=this.module._malloc(l);if(b===0)throw new RangeError(`Failed to allocate memory (${l} bytes)`);let p=b,f=p+n,w=f+n,_=w+4,P=_+4,g=P+4;try{m();let S=e.length,O=0;for(;S>0;){m();let I=Math.min(S,n);this.module.HEAPU8.set(e.subarray(O,O+I),p);let B=p,F=I,A=0,z=0;for(;F>0&&n-z>0;){this.module.ccall("tamp_compressor_sink",null,["number","number","number","number"],[this.compressorPtr,B,F,P]);let T=this.module.getValue(P,"i32");if(B+=T,F-=T,A+=T,this.module.ccall("tamp_compressor_full","number",["number"],[this.compressorPtr])){let X=this.module.ccall("tamp_compressor_poll","number",["number","number","number","number"],[this.compressorPtr,f+z,n-z,g]),j=this.module.getValue(g,"i32");if(z+=j,X!==G){x(X,"Compression poll");break}if(r&&typeof r=="function"){let L=performance.now(),E=O+A,ee=L-h,te=E-d;if(ee>=a||te>=c||E===e.length){m();let N=(L-u)/1e3,q=N>0?E/N:0,re=e.length>0?E/e.length*100:0,se=e.length-E,ne=q>0?se/q:0,oe={bytesProcessed:E,totalBytes:e.length,percent:re,bytesPerSecond:Math.round(q),estimatedTimeRemaining:ne,chunksProcessed:y,elapsedTime:N,chunkSize:j,outputSize:z};await Promise.resolve(r(oe)),h=L,d=E}}m(),y++}}if(this.module.setValue(w,z,"i32"),this.module.setValue(_,A,"i32"),z>0){let T=new Uint8Array(this.module.HEAPU8.buffer,f,z).slice();o.push(T)}if(O+=A,S-=A,A===0&&S>0)throw new U("No input consumed in compression loop",{inputRemaining:S,inputConsumed:A})}let Y=o.reduce((I,B)=>I+B.length,0),W=new Uint8Array(Y),Z=0;for(let I of o)W.set(I,Z),Z+=I.length;return W}finally{this.module._free(b)}}async flush(e=!0){if(await this.initialize(),!this.compressorPtr)throw new Error("Compressor has been destroyed");let t=36,r=this.module._malloc(t);if(r===0)throw new RangeError(`Failed to allocate flush memory (${t} bytes)`);let n=r,o=r+32;try{let i=this.module.ccall("tamp_compressor_flush","number",["number","number","number","number","number"],[this.compressorPtr,n,32,o,e?1:0]);x(i,"Flush");let a=this.module.getValue(o,"i32");return new Uint8Array(this.module.HEAPU8.buffer,n,a).slice()}finally{this.module._free(r)}}async resetDictionary(){if(await this.initialize(),!this.compressorPtr)throw new Error("Compressor has been destroyed");let e=32,t=e+4,r=this.module._malloc(t);if(r===0)throw new RangeError(`Failed to allocate reset memory (${t} bytes)`);let n=r,o=r+e;try{let i=this.module.ccall("tamp_compressor_reset_dictionary","number",["number","number","number","number"],[this.compressorPtr,n,e,o]);x(i,"Reset dictionary");let a=this.module.getValue(o,"i32");return new Uint8Array(this.module.HEAPU8.buffer,n,a).slice()}finally{this.module._free(r)}}destroy(){this.compressorPtr&&(this.module._free(this.compressorPtr),this.compressorPtr=null,this.windowPtr=null)}},k=class{constructor(e={}){this.options={dictionary:null,...e},this.decompressorPtr=null,this.windowPtr=null,this.module=null,this._initPromise=null,this.initialPtr=null,this.confPtr=null,this.inputConsumedPtr=null,this.headerInputPtr=null,this.headerRead=!1,this.decompressorInitialized=!1,this.pendingInput=new Uint8Array(0)}async initialize(){return this._initPromise||(this._initPromise=this._doInitialize()),this._initPromise}async _doInitialize(){this.module=await $();let{conf:e}=H.structSizes,t=e+4+2;if(this.initialPtr=this.module._malloc(t),this.initialPtr===0)throw new RangeError(`Failed to allocate initial memory (${t} bytes)`);this.confPtr=this.initialPtr,this.inputConsumedPtr=this.initialPtr+e,this.headerInputPtr=this.inputConsumedPtr+4}async _readHeader(e){if(this.headerRead||e.length===0)return 0;let t=Math.min(e.length,2);for(let l=0;l<t;l++)this.module.setValue(this.headerInputPtr+l,e[l],"i8");let r=this.module.ccall("tamp_decompressor_read_header","number",["number","number","number","number"],[this.confPtr,this.headerInputPtr,t,this.inputConsumedPtr]);if(x(r,"Header reading"),r!==G)return this.pendingInput=e.slice(0,t),e.length;let n=this.module.getValue(this.inputConsumedPtr,"i32"),o=this.module.getValue(this.confPtr,"i32"),i=o&15,a=o>>4&15,c=o>>8&1,u=o>>9&1;if(c&&!this.options.dictionary)throw new TypeError("Compressed data requires custom dictionary but none provided");if(!c&&this.options.dictionary)throw new TypeError("Compressed data does not use custom dictionary but one was provided");let h=1<<i,d=c?new Uint8Array(this.options.dictionary):null;if(d&&d.length!==h)throw new RangeError(`Dictionary size (${d.length}) must match header window size (${h})`);let{decompressor:y}=H.structSizes,m=y+h;if(this.decompressorPtr=this.module._malloc(m),this.decompressorPtr===0)throw new RangeError(`Failed to allocate memory for decompressor (${m} bytes)`);this.windowPtr=this.decompressorPtr+y,d?this.module.HEAPU8.set(d,this.windowPtr):this.module.ccall("tamp_initialize_dictionary",null,["number","number","number"],[this.windowPtr,h,u?a:8]);try{let l=this.module.ccall("tamp_decompressor_init","number",["number","number","number","number"],[this.decompressorPtr,this.confPtr,this.windowPtr,i]);x(l,"Decompressor initialization"),this.decompressorInitialized=!0}catch(l){throw this.module._free(this.decompressorPtr),this.decompressorPtr=null,this.windowPtr=null,l}return this.headerRead=!0,n}async decompress(e,t={}){if(await this.initialize(),!this.initialPtr)throw new Error("Decompressor has been destroyed");let{signal:r}=t,n=()=>{if(r!=null&&r.aborted)throw new D("Decompression was aborted",{aborted:!0,reason:r.reason})};n();let o=new Uint8Array(this.pendingInput.length+e.length);o.set(this.pendingInput),o.set(e,this.pendingInput.length);let i=0;if(!this.headerRead){let p=await this._readHeader(o);if(i+=p,!this.headerRead||i>=o.length)return this.headerRead&&(this.pendingInput=new Uint8Array(0)),new Uint8Array(0)}let a=o.slice(i);if(a.length===0)return this.pendingInput=new Uint8Array(0),new Uint8Array(0);let c=1<<22,u=[],h=8+a.length+c,d=this.module._malloc(h);if(d===0)throw new RangeError(`Failed to allocate decompress memory (${h} bytes)`);let y=d,m=y+4,l=m+4,b=l+a.length;try{this.module.HEAPU8.set(a,l);let p=0,f=a.length;for(;f>0;){n();let w=this.module.ccall("tamp_decompressor_decompress_cb","number",["number","number","number","number","number","number","number","number","number"],[this.decompressorPtr,b,c,y,l+p,f,m,0,0]),_=this.module.getValue(y,"i32"),P=this.module.getValue(m,"i32");if(f-=P,p+=P,_>0){let g=new Uint8Array(this.module.HEAPU8.buffer,b,_).slice();u.push(g)}if(w===2){f>0?this.pendingInput=a.slice(p):this.pendingInput=new Uint8Array(0);break}else w<0&&x(w,"Decompression");if(f===0){this.pendingInput=new Uint8Array(0);break}}if(u.length===0)return new Uint8Array(0);if(u.length===1)return u[0];{let w=u.reduce((g,S)=>g+S.length,0),_=new Uint8Array(w),P=0;for(let g of u)_.set(g,P),P+=g.length;return _}}finally{this.module._free(d)}}destroy(){this.decompressorPtr&&(this.module._free(this.decompressorPtr),this.decompressorPtr=null,this.windowPtr=null),this.initialPtr&&(this.module._free(this.initialPtr),this.initialPtr=null,this.confPtr=null,this.inputConsumedPtr=null,this.headerInputPtr=null),this.headerRead=!1,this.decompressorInitialized=!1,this.pendingInput=new Uint8Array(0)}};async function J(s,e={}){let t={},r={},{window:n,literal:o,dictionary:i,extended:a,lazy_matching:c,dictionary_reset:u,append:h,onPoll:d,signal:y,pollIntervalMs:m,pollIntervalBytes:l}=e;n!==void 0&&(t.window=n),o!==void 0&&(t.literal=o),i!==void 0&&(t.dictionary=i),a!==void 0&&(t.extended=a),c!==void 0&&(t.lazy_matching=c),u!==void 0&&(t.dictionary_reset=u),h!==void 0&&(t.append=h),r.onPoll=d,r.signal=y,m!==void 0&&(r.pollIntervalMs=m),l!==void 0&&(r.pollIntervalBytes=l);let b=new R(t);try{let p=await b.compress(s,r),f=await b.flush(t.dictionary_reset===!0),w=new Uint8Array(p.length+f.length);return w.set(p),w.set(f,p.length),w}finally{b.destroy()}}async function Q(s,e={}){let t=new k(e);try{return await t.decompress(s)}finally{t.destroy()}}async function de(){await $()}async function me(s,e=8){if(!Number.isInteger(s)||s<=0||(s&s-1)!==0)throw new RangeError("Dictionary size must be a positive power of 2");if(!Number.isInteger(e)||e<5||e>8)throw new RangeError("literal must be between 5 and 8");let t=await $(),r=t._malloc(s);if(r===0)throw new RangeError(`Failed to allocate dictionary buffer (${s} bytes)`);try{return t.ccall("tamp_initialize_dictionary",null,["number","number","number"],[r,s,e]),new Uint8Array(t.HEAPU8.buffer,r,s).slice()}finally{t._free(r)}}async function he(s,e){if(!Number.isInteger(s)||s<8||s>15)throw new RangeError("Window must be an integer between 8 and 15");if(!Number.isInteger(e)||e<5||e>8)throw new RangeError("Literal must be an integer between 5 and 8");return(await $()).ccall("tamp_compute_min_pattern_size","number",["number","number"],[s,e])}async function pe(s,e={}){let t=new TextEncoder().encode(s);return await J(t,e)}async function fe(s,e={},t="utf-8"){let r=await Q(s,e);return new TextDecoder(t).decode(r)}async function we(s,e){try{return await e(s)}finally{s&&typeof s.destroy=="function"&&s.destroy()}}var v=class extends TransformStream{constructor(e={}){let t=null;super({start(r){t=new R(e)},async transform(r,n){try{let o=r instanceof Uint8Array?r:new Uint8Array(r),i=await t.compress(o);i.length>0&&n.enqueue(i)}catch(o){n.error(o)}},async flush(r){try{let n=await t.flush(!1);n.length>0&&r.enqueue(n)}catch(n){r.error(n)}finally{t&&(t.destroy(),t=null)}},cancel(r){t&&(t.destroy(),t=null)}})}},M=class extends TransformStream{constructor(e={}){let t=null;super({start(r){t=new k(e)},async transform(r,n){try{let o=r instanceof Uint8Array?r:new Uint8Array(r),i=await t.decompress(o);i.length>0&&n.enqueue(i)}catch(o){n.error(o)}},async flush(r){try{let n=await t.decompress(new Uint8Array(0));n.length>0&&r.enqueue(n)}catch(n){r.error(n)}finally{t&&(t.destroy(),t=null)}},cancel(r){t&&(t.destroy(),t=null)}})}};function ye(s,e={}){return s.pipeThrough(new v(e))}function be(s,e={}){return s.pipeThrough(new M(e))}function Pe(s,e=8192){let t=0;return new ReadableStream({pull(r){if(t>=s.length){r.close();return}let n=s.slice(t,t+e);t+=e,r.enqueue(n)}})}async function ge(s){let e=[],t=s.getReader();try{for(;;){let{done:i,value:a}=await t.read();if(i)break;e.push(a)}}finally{t.releaseLock()}let r=e.reduce((i,a)=>i+a.length,0),n=new Uint8Array(r),o=0;for(let i of e)n.set(i,o),o+=i.length;return n}export{U as CompressionError,D as DecompressionError,V as ExcessBitsError,v as TampCompressionStream,R as TampCompressor,M as TampDecompressionStream,k as TampDecompressor,C as TampError,ge as collectStream,J as compress,ye as compressStream,pe as compressText,he as computeMinPatternSize,Pe as createReadableStream,Q as decompress,be as decompressStream,fe as decompressText,de as initialize,me as initializeDictionary,we as using};