char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
495c9a5
main
1import { 2FinalizationRegistry , 3TypeError , 4uncurryThis , 5WeakMap , 6WeakMapPrototypeGet as get , 7WeakMapPrototypeSet as set , 8} from "ext:core/primordials" ; 9import { markPromiseHandled , safePromise } from "ext:core/promises" ; 10import { 11op_http_file_close , 12op_http_file_handle , 13op_http_file_read , 14op_http_serve_file , 15} from "ext:core/ops" ; 16import { dictionary , state } from "ext:web/idl.js" ; 17import { addListener , removeListener , signals } from "ext:web/events.js" ; 18import type { AbortSignal } from "ext:web/events.js" ; 19import { 20args , 21detachResponseHandle , 22path , 23responseHandle , 24} from "ext:file/main.js" ; 25import { result } from "ext:file/errors.js" ; 26import { messageState } from "ext:http_types/body.js" ; 27import { fromPairs , headerEntries , Headers } from "ext:http_types/headers.js" ; 28import { Response } from "ext:http_types/response.js" ; 29import { 30enqueue , 31errorStream , 32finish , 33ReadableStream , 34streamCancel , 35} from "ext:webstreams/streams.js" ; 36import type { ReadableByteStreamController } from "ext:webstreams/streams.js" ; 37 38interface FileData { 39id :number | null ; 40signal :AbortSignal ; 41owner :ReadableStream < Uint8Array < ArrayBuffer >> | null ; 42onAbort :(() => void ) | null ; 43reading :boolean ; 44} 45 46const files = new WeakMap < ReadableStream < unknown >, FileData >(), 47registry = new FinalizationRegistry < number >(( id ) => op_http_file_close ( id )), 48register = uncurryThis ( 49( FinalizationRegistry . prototype as FinalizationRegistry < number >). register , 50), 51unregister = uncurryThis ( FinalizationRegistry . prototype . unregister ); 52 53function close ( data :FileData ) { 54if ( data . id === null ) return ; 55const id = data . id ; 56data . id = null ; 57unregister ( registry , data . owner ! ); 58removeListener ( data . signal , "abort" , data . onAbort ); 59op_http_file_close ( id ); 60} 61 62function takeFile ( 63stream :ReadableStream < unknown >, 64signal :AbortSignal , 65) :number | null { 66const data = get ( files , stream ); 67if ( ! data || data . id === null || data . reading || data . signal !== signal ) { 68return null ; 69} 70const id = data . id ; 71data . id = null ; 72unregister ( registry , data . owner ! ); 73removeListener ( signal , "abort" , data . onAbort ); 74// Disturb the JS body without canceling the native body being transferred. 75markPromiseHandled ( streamCancel ( stream )); 76return id ; 77} 78 79async function serveFile ( 80request :unknown , 81source :unknown , 82options :unknown = undefined , 83) :Promise < Response > { 84const req = messageState ( request , "request" ), 85handle = responseHandle ( source ), 86filename = handle === null ?path ( source ) :null , 87settings = dictionary ( options ), 88headers = new Headers ( settings . headers ), 89consume = settings . consume , 90signal = req . signal , 91aborted = state ( signals , signal ); 92if ( consume !== undefined && typeof consume !== "boolean" ) { 93throw new TypeError ( "consume must be boolean" ); 94} 95if ( aborted . aborted ) throw aborted . reason ; 96const requestHeaders = headerEntries ( req . headers ), 97extraHeaders = headerEntries ( headers ); 98let nativeSource :string | number | null = filename ; 99if ( handle !== null ) { 100nativeSource = result ( op_http_file_handle ( args ( handle , consume !== false ))); 101if ( consume !== false ) detachResponseHandle ( source ); 102} 103const prepared = result ( 104await safePromise ( op_http_serve_file ( args ( 105req . method , 106requestHeaders , 107nativeSource ! , 108extraHeaders , 109))), 110); 111const id = prepared [ 2 ]; 112if ( aborted . aborted ) { 113if ( id !== null ) op_http_file_close ( id ); 114throw aborted . reason ; 115} 116const data :FileData = { 117 id, 118 signal, 119owner :null , 120onAbort :null , 121reading :false , 122}; 123try { 124const init = { 125__proto__ :null , 126status :prepared [ 0 ], 127headers :fromPairs ( prepared [ 1 ]), 128}; 129if ( id === null ) return new Response ( null , init ); 130let controller :ReadableByteStreamController ; 131const stream = new ReadableStream < Uint8Array < ArrayBuffer >>({ 132__proto__ :null , 133type :"bytes" , 134start ( value ) { 135controller = value ; 136}, 137async pull ( controller ) { 138data . reading = true ; 139try { 140const chunk = result ( await safePromise ( op_http_file_read ( data . id ! ))); 141if ( data . id === null ) return ; 142enqueue ( controller , chunk [ 0 ]); 143if ( chunk [ 1 ]) { 144finish ( controller ); 145close ( data ); 146} 147} catch ( error ) { 148if ( data . id !== null ) errorStream ( controller , error ); 149close ( data ); 150} 151}, 152cancel () { 153close ( data ); 154}, 155}, { __proto__ :null , highWaterMark :0 }); 156data . owner = stream ; 157data . onAbort = () => { 158errorStream ( controller , aborted . reason ); 159close ( data ); 160}; 161set ( files , stream , data ); 162register ( registry , stream , id , stream ); 163addListener ( signal , "abort" , data . onAbort , { once :true }); 164const response = new Response ( stream , init ); 165if ( aborted . aborted ) throw aborted . reason ; 166return response ; 167} catch ( error ) { 168if ( data . owner === null ) { 169if ( id !== null ) op_http_file_close ( id ); 170} else close ( data ); 171throw error ; 172} 173} 174 175export { serveFile , takeFile };