char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f2c35fd
main
1import { 2ObjectCreate , 3TypedArrayPrototypeGetTag as tag , 4TypeError , 5Uint8Array , 6uncurryThis , 7WeakMapPrototypeSet as set , 8} from "ext:core/primordials" ; 9import { 10markPromiseHandled , 11promiseThen as then , 12safePromise , 13} from "ext:core/promises" ; 14import { state } from "ext:web/idl.js" ; 15import { userAgent } from "ext:web/navigator.js" ; 16import { 17op_fetch_cancel , 18op_fetch_create , 19op_fetch_read , 20op_fetch_read_all , 21op_fetch_send , 22op_fetch_write , 23} from "ext:core/ops" ; 24import { 25enqueue , 26errorStream , 27finish , 28read , 29ReadableStream , 30readerCancel , 31release , 32streamCancel , 33streamReader , 34} from "ext:webstreams/streams.js" ; 35import { addListener , removeListener , signals } from "ext:web/events.js" ; 36import { Request } from "ext:http_types/request.js" ; 37import { Response } from "ext:http_types/response.js" ; 38import { 39fromPairs , 40getHeader , 41headerEntries , 42headers , 43setHeader , 44} from "ext:http_types/headers.js" ; 45import { bodies , bodyCollectors , usable } from "ext:http_types/body.js" ; 46 47import type { RequestState } from "ext:http_types/body.js" ; 48import type { 49ReadableByteStreamController , 50ReadableStreamDefaultReader , 51} from "ext:webstreams/streams.js" ; 52import type { FetchMetadata } from "ext:core/ops" ; 53 54interface FetchResource { 55id :number ; 56cleanup () :void ; 57} 58 59const WeakRef = globalThis . WeakRef , 60FinalizationRegistry = globalThis . FinalizationRegistry ; 61const deref = uncurryThis ( 62( WeakRef . prototype as WeakRef < ReadableByteStreamController >). deref , 63), 64registerFinalizer = uncurryThis ( 65( FinalizationRegistry . prototype as FinalizationRegistry < FetchResource >) 66. register , 67); 68function failure ( error :unknown ) :TypeError { 69// Guest message getters and stringification can abort the request. 70return new TypeError (( error as { message ?:string }). message ); 71} 72const finalizer = new FinalizationRegistry < FetchResource >(({ id, cleanup}) => { 73cleanup (); 74op_fetch_cancel ( id ); 75}); 76async function fetch ( 77input :unknown , 78init :unknown = undefined , 79) :Promise < Response > { 80if ( arguments . length === 0 ) throw new TypeError ( "fetch requires input" ); 81const request = new Request ( input , init ), 82s = state ( bodies , request ) as RequestState , 83b = s . body ; 84if ( state ( signals , s . signal ). aborted ) throw state ( signals , s . signal ). reason ; 85usable ( b ); 86const id = op_fetch_create ( null ); 87let controller :WeakRef < ReadableByteStreamController > | undefined , 88uploadError :TypeError | undefined , 89uploadReader :ReadableStreamDefaultReader < unknown > | undefined ; 90const aborted = () => { 91if ( uploadReader ) { 92markPromiseHandled ( 93readerCancel ( uploadReader , state ( signals , s . signal ). reason ), 94); 95} 96op_fetch_cancel ( id ); 97const c = controller && deref ( controller ); 98if ( c ) errorStream ( c , state ( signals , s . signal ). reason ); 99}; 100addListener ( s . signal , "abort" , aborted , { once :true }); 101const cleanup = () => removeListener ( s . signal , "abort" , aborted ); 102let pending :Promise < FetchMetadata >; 103try { 104if ( getHeader ( s . headers , "user-agent" ) === null ) { 105setHeader ( s . headers , "user-agent" , userAgent ); 106} 107const entries = headerEntries ( s . headers ); 108b . used = b . stream !== null ; 109pending = op_fetch_send ([ 110id , 111s . url , 112s . method , 113entries , 114b . bytes , 115b . stream !== null && b . bytes === null , 116s . redirect , 117]); 118if ( b . bytes !== null ) markPromiseHandled ( streamCancel ( b . stream )); 119else if ( b . stream ) { 120const reader = streamReader ( b . stream ); 121uploadReader = reader ; 122then ( 123( async () => { 124try { 125while ( true ) { 126const result = await safePromise ( read ( reader )); 127if ( result . done ) break ; 128if ( tag ( result . value ) !== "Uint8Array" ) { 129throw new TypeError ( "Upload chunks must be Uint8Array" ); 130} 131// The native write still checks shared and resizable backing stores. 132if ( 133! await safePromise ( 134op_fetch_write ([ id , result . value as Uint8Array < ArrayBuffer >]), 135) 136) { 137try { 138await safePromise ( readerCancel ( reader )); 139} catch { 140// The body is no longer wanted; failing cleanup must not fail the response. 141} 142return ; 143} 144} 145await safePromise ( op_fetch_write ([ id , null ])); 146} finally { 147release ( reader ); 148uploadReader = undefined ; 149} 150})(), 151() => {}, 152( error ) => { 153uploadError = failure ( error ); 154op_fetch_cancel ( id ); 155const c = controller && deref ( controller ); 156if ( c ) errorStream ( c , uploadError ); 157}, 158); 159} 160const reply = await safePromise ( pending ); 161const status = reply [ 0 ], 162statusText = reply [ 1 ], 163list = reply [ 2 ], 164url = reply [ 3 ], 165redirected = reply [ 4 ], 166abandoned = reply [ 5 ]; 167// The upload loop only notices on its next write, which may never come in time. 168if ( abandoned && uploadReader ) { 169markPromiseHandled ( readerCancel ( uploadReader )); 170} 171if ( state ( signals , s . signal ). aborted ) { 172throw state ( signals , s . signal ). reason ; 173} 174const h = fromPairs ( list ); 175state ( headers , h ). immutable = true ; 176let stream :ReadableStream < Uint8Array < ArrayBuffer >> | null = null ; 177if ( 178s . method === "HEAD" || status === 204 || status === 205 || 179status === 304 180) { 181cleanup (); 182op_fetch_cancel ( id ); 183} else { 184let collecting = false ; 185stream = new ReadableStream < Uint8Array < ArrayBuffer >>({ 186type :"bytes" , 187start ( c ) { 188controller = new WeakRef ( c ); 189}, 190async pull ( c ) { 191try { 192const bytes = await safePromise ( 193collecting ?op_fetch_read_all ( id ) :op_fetch_read ( id ), 194); 195if ( bytes !== null && bytes . length ) enqueue ( c , bytes ); 196if ( bytes === null || collecting ) { 197cleanup (); 198finish ( c ); 199} 200} catch ( error ) { 201cleanup (); 202op_fetch_cancel ( id ); 203errorStream ( 204c , 205state ( signals , s . signal ). aborted 206 ?state ( signals , s . signal ). reason 207 :failure ( error ), 208); 209} 210}, 211cancel () { 212cleanup (); 213op_fetch_cancel ( id ); 214}, 215}, { highWaterMark :0 }); 216// Read through the stream so it still owns disturbance, locking, and errors. 217set ( bodyCollectors , stream , async ( reader , convert ) => { 218collecting = true ; 219const result = await safePromise ( read ( reader )); 220if ( ! result . done && tag ( result . value ) !== "Uint8Array" ) { 221throw new TypeError ( "Body stream must contain Uint8Array chunks" ); 222} 223// Guest promise hooks can retain stream chunks; Blob storage must not alias them. 224return { 225__proto__ :null , 226value :convert ( 227result . done 228 ?new Uint8Array () 229 :new Uint8Array ( result . value as Uint8Array ), 230), 231}; 232}); 233registerFinalizer ( finalizer , stream , { id, cleanup}); 234} 235const response = ObjectCreate ( Response . prototype ) as Response ; 236set ( bodies , response , { 237kind :"response" , 238body :{ stream, bytes :null , used :false , type :null }, 239headers :h , 240 status, 241 statusText, 242 url, 243 redirected, 244type :"basic" , 245}); 246return response ; 247} catch ( error ) { 248cleanup (); 249op_fetch_cancel ( id ); 250throw state ( signals , s . signal ). aborted 251 ?state ( signals , s . signal ). reason 252 :uploadError ?? failure ( error ); 253} 254} 255 256export { fetch };