char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
ec742cf
main
1import { safePromise } from "ext:core/promises" ; 2import { 3ArrayBuffer , 4ArrayBufferPrototypeGetResizable as resizable , 5getter , 6ObjectGetOwnPropertyDescriptor as descriptor , 7ReflectApply as apply , 8Set , 9SetPrototypeHas as has , 10TypedArrayPrototypeGetBuffer as typedArrayBuffer , 11TypedArrayPrototypeGetByteLength as typedArrayLength , 12TypedArrayPrototypeGetByteOffset as typedArrayOffset , 13TypeError , 14Uint8Array , 15uncurryThis , 16WeakMap , 17WeakMapPrototypeSet as set , 18} from "ext:core/primordials" ; 19import { 20op_compression_dispose as dispose , 21op_compression_finish as finishCodec , 22op_compression_new as newCodec , 23op_compression_transform as transformCodec , 24} from "ext:core/ops" ; 25import { installInterface } from "ext:web/interface.js" ; 26import { required , state , string } from "ext:web/idl.js" ; 27import { registerUncloneable } from "ext:web/serialization.js" ; 28import { 29ReadableStream , 30TransformStream , 31TransformStreamDefaultController , 32WritableStream , 33} from "ext:webstreams/streams.js" ; 34 35import type { 36ReadableStreamDefaultReader as Reader , 37ReadableStreamDefaultController as ReadableController , 38} from "ext:webstreams/streams.js" ; 39 40type Bytes = Uint8Array < ArrayBuffer >; 41interface CodecState { 42id :number | null ; 43owner :object ; 44stream :TransformStream < unknown , Bytes > | null ; 45readable ?:ReadableStream < Bytes >; 46writable ?:WritableStream < unknown >; 47} 48 49const isView = ArrayBuffer . isView ; 50const arrayBufferLength = getter ( ArrayBuffer . prototype , "byteLength" ); 51const dataViewBuffer = getter ( DataView . prototype , "buffer" ); 52const dataViewLength = getter ( DataView . prototype , "byteLength" ); 53const dataViewOffset = getter ( DataView . prototype , "byteOffset" ); 54const enqueue :( controller :TransformStreamDefaultController < Bytes >, chunk :Bytes ) => void = 55uncurryThis ( TransformStreamDefaultController . prototype . enqueue ); 56const readable = descriptor ( TransformStream . prototype , "readable" ) ! . get ! ; 57const writable = descriptor ( TransformStream . prototype , "writable" ) ! . get ! ; 58const getReader = uncurryThis (( ReadableStream . prototype as ReadableStream < Bytes >). getReader ); 59const read = uncurryThis (( ReadableStreamDefaultReader . prototype as Reader < Bytes >). read ); 60const readerCancel = uncurryThis ( ReadableStreamDefaultReader . prototype . cancel ); 61const enqueueReadable :( controller :ReadableController < Bytes >, chunk :Bytes ) => void = uncurryThis ( 62ReadableStreamDefaultController . prototype . enqueue , 63); 64const closeReadable = uncurryThis ( 65ReadableStreamDefaultController . prototype . close , 66); 67const getWriter = uncurryThis ( WritableStream . prototype . getWriter ); 68const write = uncurryThis ( WritableStreamDefaultWriter . prototype . write ); 69const closeWriter = uncurryThis ( WritableStreamDefaultWriter . prototype . close ); 70const abortWriter = uncurryThis ( WritableStreamDefaultWriter . prototype . abort ); 71const register :( registry :FinalizationRegistry < number >, target :WeakKey , id :number ) => void = 72uncurryThis ( FinalizationRegistry . prototype . register ); 73const compressionStates = new WeakMap < object , Required < CodecState >>(), 74decompressionStates = new WeakMap < object , Required < CodecState >>(); 75registerUncloneable ( compressionStates ); 76registerUncloneable ( decompressionStates ); 77const finalizer = new FinalizationRegistry < number >(( id ) => dispose ( id )); 78const formats = new Set ([ "deflate" , "deflate-raw" , "gzip" , "brotli" ]); 79 80function bufferSource ( value :unknown ) :Bytes { 81let buffer , offset = 0 , length ; 82if ( isView ( value )) { 83try { 84buffer = typedArrayBuffer ( value ); 85offset = typedArrayOffset ( value ); 86length = typedArrayLength ( value ); 87} catch { 88try { 89buffer = dataViewBuffer ( value ); 90offset = dataViewOffset ( value ); 91length = dataViewLength ( value ); 92} catch { 93throw new TypeError ( "Expected an attached BufferSource" ); 94} 95} 96} else { 97buffer = value ; 98try { 99length = arrayBufferLength ( buffer ); 100} catch { 101throw new TypeError ( "Expected a non-shared BufferSource" ); 102} 103} 104try { 105arrayBufferLength ( buffer ); 106} catch { 107throw new TypeError ( "Expected a non-shared BufferSource" ); 108} 109try { 110const bytes = new Uint8Array ( buffer as ArrayBuffer , offset , length ); 111return resizable ( buffer ) ?new Uint8Array ( bytes ) :bytes ; 112} catch { 113throw new TypeError ( "Expected an attached BufferSource" ); 114} 115} 116 117function disposeState ( internal :CodecState ) :void { 118const id = internal . id ; 119if ( id !== null ) { 120internal . id = null ; 121dispose ( id ); 122} 123} 124 125function create ( receiver :object , rawFormat :unknown , decompress :boolean , states :WeakMap < object , Required < CodecState >>) :void { 126const format = string ( rawFormat ); 127if ( ! has ( formats , format )) { 128throw new TypeError ( "Unsupported compression format" ); 129} 130const id = newCodec ([ format , decompress ]); 131const internal :CodecState = { id, owner :receiver , stream :null }; 132const stream = new TransformStream < unknown , Bytes >({ 133async transform ( chunk , controller ) { 134try { 135const input = bufferSource ( chunk ); 136// Cancellation can clear the handle; preserve the native rejection/catch path. 137const result = await safePromise ( transformCodec ([ internal . id ! , input ])); 138const output = result [ 0 ]; 139if ( output . byteLength !== 0 ) enqueue ( controller , output ); 140if ( result [ 1 ]) { 141throw new TypeError ( "Invalid or unsupported compressed data" ); 142} 143} catch { 144disposeState ( internal ); 145throw new TypeError ( "Invalid or unsupported compressed data" ); 146} 147}, 148async flush ( controller ) { 149if ( internal . id === null ) return ; 150try { 151const result = await safePromise ( finishCodec ( internal . id )); 152const output = result [ 0 ]; 153if ( output . byteLength !== 0 ) enqueue ( controller , output ); 154} catch { 155disposeState ( internal ); 156throw new TypeError ( "Invalid or truncated compressed data" ); 157} 158disposeState ( internal ); 159}, 160cancel () { 161disposeState ( internal ); 162}, 163}); 164const outputReader = getReader ( apply ( readable , stream , []) as ReadableStream < Bytes >); 165const inputWriter = getWriter ( apply ( writable , stream , []) as WritableStream < unknown >); 166internal . readable = new ReadableStream < Bytes >({ 167async pull ( controller ) { 168try { 169const result = await safePromise ( read ( outputReader )); 170if ( result . done ) closeReadable ( controller ); 171else enqueueReadable ( controller , result . value ); 172} catch ( error ) { 173disposeState ( internal ); 174throw error ; 175} 176}, 177cancel ( reason ) { 178disposeState ( internal ); 179return readerCancel ( outputReader , reason ); 180}, 181}); 182internal . writable = new WritableStream < unknown >({ 183write ( chunk ) { 184return write ( inputWriter , chunk ); 185}, 186close () { 187return closeWriter ( inputWriter ); 188}, 189abort ( reason ) { 190disposeState ( internal ); 191return abortWriter ( inputWriter , reason ); 192}, 193}); 194internal . stream = stream ; 195// Publish the brand only after both stream endpoints have been installed. 196set ( states , receiver , internal as Required < CodecState >); 197register ( finalizer , receiver , id ); 198} 199 200class CompressionStream { 201constructor ( format :unknown ) { 202required ( arguments . length , 1 ); 203create ( this , format , false , compressionStates ); 204} 205get readable () :ReadableStream < Bytes > { 206return state ( compressionStates , this ). readable ; 207} 208get writable () :WritableStream < unknown > { 209return state ( compressionStates , this ). writable ; 210} 211} 212 213class DecompressionStream { 214constructor ( format :unknown ) { 215required ( arguments . length , 1 ); 216create ( this , format , true , decompressionStates ); 217} 218get readable () :ReadableStream < Bytes > { 219return state ( decompressionStates , this ). readable ; 220} 221get writable () :WritableStream < unknown > { 222return state ( decompressionStates , this ). writable ; 223} 224} 225 226export type { CompressionStream , DecompressionStream }; 227 228for ( const ctor of [ CompressionStream , DecompressionStream ]) { 229installInterface ( ctor . name , ctor ); 230}