export const XSolidStartStatusCodeHeader = "x-solidstart-status-code"; export const XSolidStartLocationHeader = "x-solidstart-location"; export const LocationHeader = "Location"; export const ContentTypeHeader = "content-type"; export const XSolidStartResponseTypeHeader = "x-solidstart-response-type"; export const XSolidStartContentTypeHeader = "x-solidstart-content-type"; export const XSolidStartOrigin = "x-solidstart-origin"; export const JSONResponseType = "application/json"; /** * A JSON response. Converts `data` to JSON and sets the `Content-Type` header. */ export function json(data: Data, init: number | ResponseInit = {}): Response { let responseInit: any = init; if (typeof init === "number") { responseInit = { status: init }; } let headers = new Headers(responseInit.headers); if (!headers.has(ContentTypeHeader)) { headers.set(ContentTypeHeader, "application/json; charset=utf-8"); } const response = new Response(JSON.stringify(data), { ...responseInit, headers }); return response; } /** * A redirect response. Sets the status code and the `Location` header. * Defaults to "302 Found". */ export function redirect(url: string, init: number | ResponseInit = 302): Response { let responseInit = init; if (typeof responseInit === "number") { responseInit = { status: responseInit }; } else if (typeof responseInit.status === "undefined") { responseInit.status = 302; } if (url === "") { url = "/"; } if (process.env.NODE_ENV === "development") { if (url.startsWith(".")) { throw new Error("Relative URLs are not allowed in redirect"); } } const response = new Response(null, { ...responseInit, headers: { ...responseInit.headers, [XSolidStartLocationHeader]: url, [LocationHeader]: url } }); return response; } export function eventStream( request: Request, init: (send: (event: string, data: object) => void) => () => void ) { let stream = new ReadableStream({ start(controller) { let encoder = new TextEncoder(); let send = (event: string, data: object) => { controller.enqueue(encoder.encode("event: " + event + "\n")); controller.enqueue(encoder.encode("data: " + data + "\n" + "\n")); }; let cleanup = init(send); let closed = false; let close = () => { if (closed) return; cleanup(); closed = true; request.signal.removeEventListener("abort", close); controller.close(); }; request.signal.addEventListener("abort", close); if (request.signal.aborted) { close(); return; } } }); return new Response(stream, { headers: { "Content-Type": "text/event-stream" } }); } export function isResponse(value: any): value is Response { return ( value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined" ); } const redirectStatusCodes = new Set([204, 301, 302, 303, 307, 308]); export function isRedirectResponse(response: Response): boolean { return response && response instanceof Response && redirectStatusCodes.has(response.status); } export class ResponseError extends Error implements Response { status: number; headers: Headers; name = "ResponseError"; ok: boolean; statusText: string; redirected: boolean; url: string; constructor(response: Response) { let message = JSON.stringify({ $type: "response", status: response.status, message: response.statusText, headers: [...response.headers.entries()] }); super(message); this.status = response.status; this.headers = new Map([...response.headers.entries()]) as any as Headers; this.url = response.url; this.ok = response.ok; this.statusText = response.statusText; this.redirected = response.redirected; this.bodyUsed = false; this.type = response.type; this.response = () => response; } response: () => Response; type: ResponseType; clone(): Response { return this.response(); } get body(): ReadableStream { return this.response().body; } bodyUsed: boolean; async arrayBuffer(): Promise { return await this.response().arrayBuffer(); } async blob(): Promise { return await this.response().blob(); } async formData(): Promise { return await this.response().formData(); } async text() { return await this.response().text(); } async json() { return await this.response().json(); } }