export async function readBytes( chunks: AsyncIterable | Iterable, maxBytes: number, ): Promise { const parts: Uint8Array[] = []; let size = 0; for await (const chunk of chunks) { size += chunk.byteLength; if (size > maxBytes) throw new Error("Body too large"); parts.push(chunk); } const bytes = new Uint8Array(size); let offset = 0; for (const part of parts) { bytes.set(part, offset); offset += part.byteLength; } return bytes; } export const cookie = ( name: string, value: string, secure: boolean, maxAge: number, ) => `${name}=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}${ secure ? "; Secure" : "" }`; export const readCookie = (req: Request, name: string) => new RegExp(`(?:^|;\\s*)${name}=([^;]*)`) .exec(req.headers.get("cookie") ?? "")?.[1]; // An origin with nothing else: no credentials, path, query, or fragment. export const isBareOrigin = (url: URL) => ["https:", "http:"].includes(url.protocol) && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash; export class HttpError extends Error { constructor( readonly status: number, message: string, readonly headers: HeadersInit = {}, ) { super(message); } response(): Response { return new Response(this.message, { status: this.status, headers: this.headers, }); } } export function fail(status: number, message: string): never { throw new HttpError(status, message); } export const redirect = (path: string, ...cookies: string[]): Response => { const res = new Response(null, { status: 303, headers: { location: path } }); for (const value of cookies) res.headers.append("set-cookie", value); return res; };