import { Effect } from "effect"; export class RequestBodyTooLargeError extends Error { constructor(readonly limit: number) { super(`Request body exceeds ${limit} bytes`); this.name = "RequestBodyTooLargeError"; } } const bodyBytes = async (request: Request, limit: number): Promise => { const declared = Number(request.headers.get("content-length") ?? 0); if (Number.isFinite(declared) && declared > limit) throw new RequestBodyTooLargeError(limit); if (!request.body) return new ArrayBuffer(0); const reader = request.body.getReader(); const chunks: Uint8Array[] = []; let total = 0; try { while (true) { const next = await reader.read(); if (next.done) break; total += next.value.byteLength; if (total > limit) { await reader.cancel().catch(() => undefined); throw new RequestBodyTooLargeError(limit); } chunks.push(next.value); } } finally { reader.releaseLock(); } const body = new ArrayBuffer(total); const bytes = new Uint8Array(body); let offset = 0; for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; } return body; }; export const readBoundedRequestBody = (request: Request, limit: number): Promise => Effect.runPromise( Effect.tryPromise({ try: () => bodyBytes(request, limit), catch: (error) => error instanceof RequestBodyTooLargeError ? error : new Error(error instanceof Error ? error.message : String(error)), }), ); export const boundedFormData = async (request: Request, limit: number): Promise => { const body = await readBoundedRequestBody(request, limit); return new Request(request.url, { method: request.method, headers: request.headers, body, }).formData(); };