/** * Sync snapshots of async-bodied web types (`Request`, `Response`, `Blob`, * `File`) after their bodies have been drained. msgpackr's extension `write` * callback is synchronous, so anything with an async body must be pre-processed * into one of these before packing. */ /** * `Uint8Array` — the shape `BodyInit`/`BlobPart` expect under DOM * lib. */ type Bytes = Uint8Array const METHODS_WITHOUT_BODY = new Set(["GET", "HEAD"]) /** * Wraps an `ArrayBuffer` in the byte-array shape accepted by web body APIs. * * @param buffer - Buffer to expose as bytes. */ function toBytes(buffer: ArrayBuffer): Bytes { return new Uint8Array(buffer) } /** A serializable snapshot of a request and its buffered body. */ export class BufferedRequest { /** * Creates a buffered request snapshot. * * @param url - Original request URL. * @param method - Original HTTP method. * @param headers - Original headers as string entries. * @param body - Buffered body, or `null` when the request has no body. */ constructor( public readonly url: string, public readonly method: string, public readonly headers: Record, public readonly body: Bytes | null, ) {} /** * Drains a request into a serializable snapshot. * * @param req - Request to buffer. */ static async from(req: Request): Promise { return new BufferedRequest( req.url, req.method, Object.fromEntries(req.headers), req.body !== null && !METHODS_WITHOUT_BODY.has(req.method) ? toBytes(await req.arrayBuffer()) : null, ) } /** Reconstructs a request from this snapshot. */ toRequest(): Request { return new Request(this.url, { method: this.method, headers: new Headers(this.headers), body: this.body !== null && !METHODS_WITHOUT_BODY.has(this.method) ? this.body : undefined, }) } } /** A serializable snapshot of a response and its buffered body. */ export class BufferedResponse { /** * Creates a buffered response snapshot. * * @param status - Original response status code. * @param statusText - Original response status text. * @param headers - Original headers as string entries. * @param body - Buffered body, or `null` when the response has no body. */ constructor( public readonly status: number, public readonly statusText: string, public readonly headers: Record, public readonly body: Bytes | null, ) {} /** * Drains a response into a serializable snapshot. * * @param res - Response to buffer. */ static async from(res: Response): Promise { return new BufferedResponse( res.status, res.statusText, Object.fromEntries(res.headers), res.body !== null ? toBytes(await res.arrayBuffer()) : null, ) } /** Reconstructs a response from this snapshot. */ toResponse(): Response { return new Response(this.body, { status: this.status, statusText: this.statusText, headers: new Headers(this.headers), }) } } /** A serializable snapshot of a blob and its buffered bytes. */ export class BufferedBlob { /** * Creates a buffered blob snapshot. * * @param type - Original media type. * @param bytes - Buffered blob contents. */ constructor( public readonly type: string, public readonly bytes: Bytes, ) {} /** * Drains a blob into a serializable snapshot. * * @param blob - Blob to buffer. */ static async from(blob: Blob): Promise { return new BufferedBlob(blob.type, toBytes(await blob.arrayBuffer())) } /** Reconstructs a blob from this snapshot. */ toBlob(): Blob { return new Blob([this.bytes], { type: this.type }) } } /** A serializable snapshot of a file and its buffered bytes. */ export class BufferedFile { /** * Creates a buffered file snapshot. * * @param name - Original file name. * @param type - Original media type. * @param lastModified - Original modification timestamp. * @param bytes - Buffered file contents. */ constructor( public readonly name: string, public readonly type: string, public readonly lastModified: number, public readonly bytes: Bytes, ) {} /** * Drains a file into a serializable snapshot. * * @param file - File to buffer. */ static async from(file: File): Promise { return new BufferedFile( file.name, file.type, file.lastModified, toBytes(await file.arrayBuffer()), ) } /** Reconstructs a file from this snapshot. */ toFile(): File { return new File([this.bytes], this.name, { type: this.type, lastModified: this.lastModified, }) } }