UNPKG

@brianpugh/tamp

Version:

Tamp compression library for JavaScript/TypeScript using WebAssembly

2 lines (1 loc) 12.9 kB
import st from"./tamp-module.mjs";var X=0,nt=-1,rt=-2,it=-3,ot=-4,R=class extends Error{constructor(t,e,n={}){super(e),this.name="TampError",this.code=t,this.details=n}},F=class extends R{constructor(t="Provided data has more bits than expected literal bits",e={}){super(-2,t,e),this.name="ExcessBitsError"}},x=class extends R{constructor(t="Compression operation failed",e={}){super(-1,t,e),this.name="CompressionError"}},T=class extends R{constructor(t="Decompression operation failed",e={}){super(-1,t,e),this.name="DecompressionError"}},M=class{#t=null;#e=null;#s=null;async initialize(){return this.#e||(this.#e=this.#n()),await this.#e}async#n(){return this.#t=await st(),this.#s={compressor:this.#t.ccall("tamp_compressor_sizeof","number",[],[]),decompressor:this.#t.ccall("tamp_decompressor_sizeof","number",[],[]),conf:this.#t.ccall("tamp_conf_sizeof","number",[],[])},this.#t}get module(){return this.#t}get structSizes(){return this.#s}get isInitialized(){return this.#t!==null}},D=new M;async function k(){return await D.initialize()}function C(s,t){if(s<0){let e={operation:t,code:s};switch(s){case rt:throw new F(`${t}: Symbol has more bits than configured literal size`,e);case it:throw new RangeError(`${t}: Invalid configuration parameters`);case ot:throw new RangeError(`${t}: Out-of-bounds access detected in compressed data`);case nt:default:throw t.toLowerCase().includes("compress")?new x(`${t}: Operation failed`,e):t.toLowerCase().includes("decompress")?new T(`${t}: Operation failed`,e):new R(s,`${t}: Unknown error`,e)}}return s}var N=class{constructor(t={}){if(this.options={window:10,literal:8,dictionary:null,extended:!0,lazy_matching:!1,dictionary_reset:!1,append:!1,...t},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 k();let t=1<<this.options.window,e=this.options.dictionary?new Uint8Array(this.options.dictionary):null;if(e&&e.length!==t)throw new RangeError(`Dictionary size (${e.length}) must match window size (${t})`);let{compressor:n,conf:i}=D.structSizes,r=n+i+t;if(this.compressorPtr=this.module._malloc(r),this.compressorPtr===0)throw new RangeError(`Failed to allocate memory for compressor (${r} bytes)`);let o=this.compressorPtr+n;this.windowPtr=o+i,e?this.module.HEAPU8.set(e,this.windowPtr):this.module.ccall("tamp_initialize_dictionary",null,["number","number","number"],[this.windowPtr,t,this.options.extended?this.options.literal:8]);try{let l=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(o,l,"i32");let c=this.module.ccall("tamp_compressor_init","number",["number","number","number"],[this.compressorPtr,o,this.windowPtr]);C(c,"Compressor initialization")}catch(l){throw this.module._free(this.compressorPtr),this.compressorPtr=null,this.windowPtr=null,l}}async compress(t,e={}){let n=e.onPoll;if(await this.initialize(),!this.compressorPtr)throw new Error("Compressor has been destroyed");let i=1<<20,r=[],{signal:o,pollIntervalMs:l=100,pollIntervalBytes:c=65536}=e,u=performance.now(),m=u,d=0,y=0,h=()=>{if(o!=null&&o.aborted)throw new x("Compression was aborted",{aborted:!0,reason:o.reason})},a=i+i+16,b=this.module._malloc(a);if(b===0)throw new RangeError(`Failed to allocate memory (${a} bytes)`);let p=b,f=p+i,w=f+i,_=w+4,P=_+4,g=P+4;try{h();let S=t.length,$=0;for(;S>0;){h();let I=Math.min(S,i);this.module.HEAPU8.set(t.subarray($,$+I),p);let O=p,V=I,E=0,z=0;for(;V>0&&i-z>0;){this.module.ccall("tamp_compressor_sink",null,["number","number","number","number"],[this.compressorPtr,O,V,P]);let U=this.module.getValue(P,"i32");if(O+=U,V-=U,E+=U,this.module.ccall("tamp_compressor_full","number",["number"],[this.compressorPtr])){let Z=this.module.ccall("tamp_compressor_poll","number",["number","number","number","number"],[this.compressorPtr,f+z,i-z,g]),q=this.module.getValue(g,"i32");if(z+=q,Z!==X){C(Z,"Compression poll");break}if(n&&typeof n=="function"){let B=performance.now(),A=$+E,G=B-m,J=A-d;if(G>=l||J>=c||A===t.length){h();let H=(B-u)/1e3,v=H>0?A/H:0,Q=t.length>0?A/t.length*100:0,Y=t.length-A,tt=v>0?Y/v:0,et={bytesProcessed:A,totalBytes:t.length,percent:Q,bytesPerSecond:Math.round(v),estimatedTimeRemaining:tt,chunksProcessed:y,elapsedTime:H,chunkSize:q,outputSize:z};await Promise.resolve(n(et)),m=B,d=A}}h(),y++}}if(this.module.setValue(w,z,"i32"),this.module.setValue(_,E,"i32"),z>0){let U=new Uint8Array(this.module.HEAPU8.buffer,f,z).slice();r.push(U)}if($+=E,S-=E,E===0&&S>0)throw new x("No input consumed in compression loop",{inputRemaining:S,inputConsumed:E})}let j=r.reduce((I,O)=>I+O.length,0),K=new Uint8Array(j),W=0;for(let I of r)K.set(I,W),W+=I.length;return K}finally{this.module._free(b)}}async flush(t=!0){if(await this.initialize(),!this.compressorPtr)throw new Error("Compressor has been destroyed");let e=36,n=this.module._malloc(e);if(n===0)throw new RangeError(`Failed to allocate flush memory (${e} bytes)`);let i=n,r=n+32;try{let o=this.module.ccall("tamp_compressor_flush","number",["number","number","number","number","number"],[this.compressorPtr,i,32,r,t?1:0]);C(o,"Flush");let l=this.module.getValue(r,"i32");return new Uint8Array(this.module.HEAPU8.buffer,i,l).slice()}finally{this.module._free(n)}}async resetDictionary(){if(await this.initialize(),!this.compressorPtr)throw new Error("Compressor has been destroyed");let t=32,e=t+4,n=this.module._malloc(e);if(n===0)throw new RangeError(`Failed to allocate reset memory (${e} bytes)`);let i=n,r=n+t;try{let o=this.module.ccall("tamp_compressor_reset_dictionary","number",["number","number","number","number"],[this.compressorPtr,i,t,r]);C(o,"Reset dictionary");let l=this.module.getValue(r,"i32");return new Uint8Array(this.module.HEAPU8.buffer,i,l).slice()}finally{this.module._free(n)}}destroy(){this.compressorPtr&&(this.module._free(this.compressorPtr),this.compressorPtr=null,this.windowPtr=null)}},L=class{constructor(t={}){this.options={dictionary:null,...t},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 k();let{conf:t}=D.structSizes,e=t+4+2;if(this.initialPtr=this.module._malloc(e),this.initialPtr===0)throw new RangeError(`Failed to allocate initial memory (${e} bytes)`);this.confPtr=this.initialPtr,this.inputConsumedPtr=this.initialPtr+t,this.headerInputPtr=this.inputConsumedPtr+4}async _readHeader(t){if(this.headerRead||t.length===0)return 0;let e=Math.min(t.length,2);for(let a=0;a<e;a++)this.module.setValue(this.headerInputPtr+a,t[a],"i8");let n=this.module.ccall("tamp_decompressor_read_header","number",["number","number","number","number"],[this.confPtr,this.headerInputPtr,e,this.inputConsumedPtr]);if(C(n,"Header reading"),n!==X)return this.pendingInput=t.slice(0,e),t.length;let i=this.module.getValue(this.inputConsumedPtr,"i32"),r=this.module.getValue(this.confPtr,"i32"),o=r&15,l=r>>4&15,c=r>>8&1,u=r>>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 m=1<<o,d=c?new Uint8Array(this.options.dictionary):null;if(d&&d.length!==m)throw new RangeError(`Dictionary size (${d.length}) must match header window size (${m})`);let{decompressor:y}=D.structSizes,h=y+m;if(this.decompressorPtr=this.module._malloc(h),this.decompressorPtr===0)throw new RangeError(`Failed to allocate memory for decompressor (${h} 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,m,u?l:8]);try{let a=this.module.ccall("tamp_decompressor_init","number",["number","number","number","number"],[this.decompressorPtr,this.confPtr,this.windowPtr,o]);C(a,"Decompressor initialization"),this.decompressorInitialized=!0}catch(a){throw this.module._free(this.decompressorPtr),this.decompressorPtr=null,this.windowPtr=null,a}return this.headerRead=!0,i}async decompress(t,e={}){if(await this.initialize(),!this.initialPtr)throw new Error("Decompressor has been destroyed");let{signal:n}=e,i=()=>{if(n!=null&&n.aborted)throw new T("Decompression was aborted",{aborted:!0,reason:n.reason})};i();let r=new Uint8Array(this.pendingInput.length+t.length);r.set(this.pendingInput),r.set(t,this.pendingInput.length);let o=0;if(!this.headerRead){let p=await this._readHeader(r);if(o+=p,!this.headerRead||o>=r.length)return this.headerRead&&(this.pendingInput=new Uint8Array(0)),new Uint8Array(0)}let l=r.slice(o);if(l.length===0)return this.pendingInput=new Uint8Array(0),new Uint8Array(0);let c=1<<22,u=[],m=8+l.length+c,d=this.module._malloc(m);if(d===0)throw new RangeError(`Failed to allocate decompress memory (${m} bytes)`);let y=d,h=y+4,a=h+4,b=a+l.length;try{this.module.HEAPU8.set(l,a);let p=0,f=l.length;for(;f>0;){i();let w=this.module.ccall("tamp_decompressor_decompress_cb","number",["number","number","number","number","number","number","number","number","number"],[this.decompressorPtr,b,c,y,a+p,f,h,0,0]),_=this.module.getValue(y,"i32"),P=this.module.getValue(h,"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=l.slice(p):this.pendingInput=new Uint8Array(0);break}else w<0&&C(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 lt(s,t={}){let e={},n={},{window:i,literal:r,dictionary:o,extended:l,lazy_matching:c,dictionary_reset:u,append:m,onPoll:d,signal:y,pollIntervalMs:h,pollIntervalBytes:a}=t;i!==void 0&&(e.window=i),r!==void 0&&(e.literal=r),o!==void 0&&(e.dictionary=o),l!==void 0&&(e.extended=l),c!==void 0&&(e.lazy_matching=c),u!==void 0&&(e.dictionary_reset=u),m!==void 0&&(e.append=m),n.onPoll=d,n.signal=y,h!==void 0&&(n.pollIntervalMs=h),a!==void 0&&(n.pollIntervalBytes=a);let b=new N(e);try{let p=await b.compress(s,n),f=await b.flush(e.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 at(s,t={}){let e=new L(t);try{return await e.decompress(s)}finally{e.destroy()}}async function ht(){await k()}async function mt(s,t=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(t)||t<5||t>8)throw new RangeError("literal must be between 5 and 8");let e=await k(),n=e._malloc(s);if(n===0)throw new RangeError(`Failed to allocate dictionary buffer (${s} bytes)`);try{return e.ccall("tamp_initialize_dictionary",null,["number","number","number"],[n,s,t]),new Uint8Array(e.HEAPU8.buffer,n,s).slice()}finally{e._free(n)}}async function pt(s,t){if(!Number.isInteger(s)||s<8||s>15)throw new RangeError("Window must be an integer between 8 and 15");if(!Number.isInteger(t)||t<5||t>8)throw new RangeError("Literal must be an integer between 5 and 8");return(await k()).ccall("tamp_compute_min_pattern_size","number",["number","number"],[s,t])}async function ft(s,t={}){let e=new TextEncoder().encode(s);return await lt(e,t)}async function wt(s,t={},e="utf-8"){let n=await at(s,t);return new TextDecoder(e).decode(n)}async function yt(s,t){try{return await t(s)}finally{s&&typeof s.destroy=="function"&&s.destroy()}}export{x as CompressionError,T as DecompressionError,F as ExcessBitsError,N as TampCompressor,L as TampDecompressor,R as TampError,lt as compress,ft as compressText,pt as computeMinPatternSize,at as decompress,wt as decompressText,ht as initialize,mt as initializeDictionary,yt as using};