UNPKG

@elysiajs/node

Version:

Elysia adapter to run Elysia on Node.js

594 lines (591 loc) 19.5 kB
// src/handle.ts import { FastResponse as Response2 } from "srvx"; import { createStreamHandler as createStreamHandler2, handleSet, responseToSetHeaders as responseToSetHeaders2, streamResponse as streamResponse2 } from "elysia/adapter/utils"; // src/utils.ts import { FastResponse as Response } from "srvx"; import { isNotEmpty, StatusMap } from "elysia/utils"; import { createStreamHandler, responseToSetHeaders, streamResponse } from "elysia/adapter/utils"; var handleFile = (response, set2) => { if (response instanceof Promise) return response.then((res) => handleFile(res, set2)); const size = response.size; const immutable = set2 && (set2.status === 206 || set2.status === 304 || set2.status === 412 || set2.status === 416); const defaultHeader = immutable ? { "transfer-encoding": "chunked" } : { "accept-ranges": "bytes", "content-range": size ? `bytes 0-${size - 1}/${size}` : void 0, "transfer-encoding": "chunked" }; if (!set2 && !size) return new Response(response); if (!set2) return new Response(response, { headers: defaultHeader }); if (set2.headers instanceof Headers) { let setHeaders = defaultHeader; setHeaders = {}; for (const [key, value] of set2.headers.entries()) if (key in set2.headers) setHeaders[key] = value; if (immutable) { delete set2.headers["content-length"]; delete set2.headers["accept-ranges"]; } return new Response(response, { status: set2.status, headers: setHeaders }); } if (isNotEmpty(set2.headers)) return new Response(response, { status: set2.status, headers: Object.assign(defaultHeader, set2.headers) }); return new Response(response, { status: set2.status, headers: defaultHeader }); }; function mergeHeaders(responseHeaders, setHeaders) { const headers = new Headers(Object.fromEntries(responseHeaders.entries())); if (setHeaders instanceof Headers) for (const key of setHeaders.keys()) { if (key === "set-cookie") { if (headers.has("set-cookie")) continue; for (const cookie of setHeaders.getSetCookie()) headers.append("set-cookie", cookie); } else if (!responseHeaders.has(key)) headers.set(key, setHeaders?.get(key) ?? ""); } else for (const key in setHeaders) if (key === "set-cookie") headers.append(key, setHeaders[key]); else if (!responseHeaders.has(key)) headers.set(key, setHeaders[key]); return headers; } function mergeStatus(responseStatus, setStatus) { if (typeof setStatus === "string") setStatus = StatusMap[setStatus]; if (responseStatus === 200) return setStatus; return responseStatus; } var createResponseHandler = (handler) => { const handleStream2 = createStreamHandler(handler); return (response, set2, request) => { const newResponse = new Response(response.body, { headers: mergeHeaders(response.headers, set2.headers), status: mergeStatus(response.status, set2.status) }); if (!newResponse.headers.has("content-length") && newResponse.headers.get("transfer-encoding") === "chunked") return handleStream2( streamResponse(newResponse), responseToSetHeaders(newResponse, set2), request, // @ts-ignore true // don't auto-format SSE for pre-formatted Response ); return newResponse; }; }; // src/handle.ts import { mime } from "elysia/universal/file"; import { isNotEmpty as isNotEmpty2 } from "elysia/utils"; import { Cookie } from "elysia/cookies"; import { ElysiaCustomStatusResponse } from "elysia/error"; var handleElysiaFile = (file, set2 = { headers: {} }) => { const path = file.path; const contentType = mime[path.slice(path.lastIndexOf(".") + 1)]; if (contentType) set2.headers["content-type"] = contentType; if (file.stats && set2 && set2.status !== 206 && set2.status !== 304 && set2.status !== 412 && set2.status !== 416) return file.stats.then((stat) => { const size = stat.size; if (size !== void 0) { set2.headers["content-range"] = `bytes 0-${size - 1}/${size}`; set2.headers["content-length"] = size; } return handleFile(file.value, set2); }); return handleFile(file.value, set2); }; var mapResponse = (response, set2, request) => { if (isNotEmpty2(set2.headers) || set2.status !== 200 || set2.cookie) { handleSet(set2); switch (response?.constructor?.name) { case "String": set2.headers["content-type"] = "text/plain"; return new Response2(response, set2); case "Array": case "Object": set2.headers["content-type"] = "application/json"; return new Response2(JSON.stringify(response), set2); case "ElysiaFile": return handleElysiaFile(response, set2); case "File": return handleFile(response, set2); case "Blob": return handleFile(response, set2); case "ElysiaCustomStatusResponse": set2.status = response.code; return mapResponse( response.response, set2, request ); case "ReadableStream": if (!set2.headers["content-type"]?.startsWith( "text/event-stream" )) set2.headers["content-type"] = "text/event-stream; charset=utf-8"; request?.signal?.addEventListener( "abort", { handleEvent() { if (request?.signal && !request?.signal?.aborted) response.cancel(); } }, { once: true } ); return new Response2(response, set2); case void 0: if (!response) return new Response2(null, set2); return new Response2(JSON.stringify(response), set2); case "Response": return handleResponse(response, set2, request); case "Error": return errorToResponse(response, set2); case "Promise": return response.then( (x) => mapResponse(x, set2, request) ); case "Function": return mapResponse(response(), set2, request); case "Number": case "Boolean": return new Response2( response.toString(), set2 ); case "Cookie": if (response instanceof Cookie) return new Response2(response.value, set2); return new Response2(response?.toString(), set2); case "FormData": return new Response2(response, set2); default: if (response instanceof Response2) return handleResponse(response, set2, request); if (response instanceof Promise) return response.then((x) => mapResponse(x, set2)); if (response instanceof Error) return errorToResponse(response, set2); if (response instanceof ElysiaCustomStatusResponse) { set2.status = response.code; return mapResponse( response.response, set2, request ); } if (typeof response?.next === "function") return handleStream(response, set2, request); if (typeof response?.then === "function") return response.then((x) => mapResponse(x, set2)); if (typeof response?.toResponse === "function") return mapResponse(response.toResponse(), set2); if ("charCodeAt" in response) { const code = response.charCodeAt(0); if (code === 123 || code === 91) { if (!set2.headers["Content-Type"]) set2.headers["Content-Type"] = "application/json"; return new Response2( JSON.stringify(response), set2 ); } } return new Response2(response, set2); } } if (response instanceof Response2 && !response.headers.has("content-length") && response.headers.get("transfer-encoding") === "chunked") return handleStream( streamResponse2(response), responseToSetHeaders2(response, set2), request ); if ( // @ts-expect-error typeof response?.next === "function" || response instanceof ReadableStream ) return handleStream(response, set2, request); return mapCompactResponse(response, request); }; var mapEarlyResponse = (response, set2, request) => { if (response === void 0 || response === null) return; if (isNotEmpty2(set2.headers) || set2.status !== 200 || set2.cookie) { handleSet(set2); switch (response?.constructor?.name) { case "String": set2.headers["content-type"] = "text/plain"; return new Response2(response, set2); case "Array": case "Object": set2.headers["content-type"] = "application/json"; return new Response2(JSON.stringify(response), set2); case "ElysiaFile": return handleElysiaFile(response, set2); case "File": return handleFile(response, set2); case "Blob": return handleFile(response, set2); case "ElysiaCustomStatusResponse": set2.status = response.code; return mapEarlyResponse( response.response, set2, request ); case "ReadableStream": if (!set2.headers["content-type"]?.startsWith( "text/event-stream" )) set2.headers["content-type"] = "text/event-stream; charset=utf-8"; request?.signal?.addEventListener( "abort", { handleEvent() { if (request?.signal && !request?.signal?.aborted) response.cancel(); } }, { once: true } ); return new Response2(response, set2); case void 0: if (!response) return; return new Response2(JSON.stringify(response), set2); case "Response": return handleResponse(response, set2, request); case "Promise": return response.then( (x) => mapEarlyResponse(x, set2) ); case "Error": return errorToResponse(response, set2); case "Function": return mapEarlyResponse(response(), set2); case "Number": case "Boolean": return new Response2( response.toString(), set2 ); case "FormData": return new Response2(response); case "Cookie": if (response instanceof Cookie) return new Response2(response.value, set2); return new Response2(response?.toString(), set2); default: if (response instanceof Response2) return handleResponse(response, set2, request); if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set2)); if (response instanceof Error) return errorToResponse(response, set2); if (response instanceof ElysiaCustomStatusResponse) { set2.status = response.code; return mapEarlyResponse( response.response, set2, request ); } if (typeof response?.next === "function") return handleStream(response, set2, request); if (typeof response?.then === "function") return response.then((x) => mapEarlyResponse(x, set2)); if (typeof response?.toResponse === "function") return mapEarlyResponse(response.toResponse(), set2); if ("charCodeAt" in response) { const code = response.charCodeAt(0); if (code === 123 || code === 91) { if (!set2.headers["Content-Type"]) set2.headers["Content-Type"] = "application/json"; return new Response2( JSON.stringify(response), set2 ); } } return new Response2(response, set2); } } else switch (response?.constructor?.name) { case "String": set2.headers["content-type"] = "text/plain"; return new Response2(response); case "Array": case "Object": set2.headers["content-type"] = "application/json"; return new Response2(JSON.stringify(response), set2); case "ElysiaFile": return handleElysiaFile(response, set2); case "File": return handleFile(response, set2); case "Blob": return handleFile(response, set2); case "ElysiaCustomStatusResponse": set2.status = response.code; return mapEarlyResponse( response.response, set2, request ); case "ReadableStream": request?.signal?.addEventListener( "abort", { handleEvent() { if (request?.signal && !request?.signal?.aborted) response.cancel(); } }, { once: true } ); return new Response2(response, { headers: { "Content-Type": "text/event-stream; charset=utf-8" } }); case void 0: if (!response) return new Response2(""); return new Response2(JSON.stringify(response), { headers: { "content-type": "application/json" } }); case "Response": if (!response.headers.has("content-length") && response.headers.get("transfer-encoding") === "chunked") return handleStream( streamResponse2(response), responseToSetHeaders2(response), request ); return response; case "Promise": return response.then((x) => { const r = mapEarlyResponse(x, set2); if (r !== void 0) return r; }); case "Error": return errorToResponse(response, set2); case "Function": return mapCompactResponse(response(), request); case "Number": case "Boolean": return new Response2(response.toString()); case "Cookie": if (response instanceof Cookie) return new Response2(response.value, set2); return new Response2(response?.toString(), set2); case "FormData": return new Response2(response); default: if (response instanceof Response2) return response; if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set2)); if (response instanceof Error) return errorToResponse(response, set2); if (response instanceof ElysiaCustomStatusResponse) { set2.status = response.code; return mapEarlyResponse( response.response, set2, request ); } if (typeof response?.next === "function") return handleStream(response, set2, request); if (typeof response?.then === "function") return response.then((x) => mapEarlyResponse(x, set2)); if (typeof response?.toResponse === "function") return mapEarlyResponse(response.toResponse(), set2); if ("charCodeAt" in response) { const code = response.charCodeAt(0); if (code === 123 || code === 91) { if (!set2.headers["Content-Type"]) set2.headers["Content-Type"] = "application/json"; return new Response2( JSON.stringify(response), set2 ); } } return new Response2(response); } }; var mapCompactResponse = (response, request) => { switch (response?.constructor?.name) { case "String": return new Response2(response, { headers: { "Content-Type": "text/plain" } }); case "Object": case "Array": return new Response2(JSON.stringify(response), { headers: { "Content-Type": "application/json" } }); case "ElysiaFile": return handleElysiaFile(response); case "File": return handleFile(response); case "Blob": return handleFile(response); case "ElysiaCustomStatusResponse": return mapResponse( response.response, { status: response.code, headers: {} } ); case "ReadableStream": request?.signal?.addEventListener( "abort", { handleEvent() { if (request?.signal && !request?.signal?.aborted) response.cancel(); } }, { once: true } ); return new Response2(response, { headers: { "Content-Type": "text/event-stream; charset=utf-8" } }); case void 0: if (!response) return new Response2(""); return new Response2(JSON.stringify(response), { headers: { "content-type": "application/json" } }); case "Response": if (response.headers.get("transfer-encoding") === "chunked") return handleStream( streamResponse2(response), responseToSetHeaders2(response), request ); return response; case "Error": return errorToResponse(response); case "Promise": return response.then( (x) => mapCompactResponse(x, request) ); // ? Maybe response or Blob case "Function": return mapCompactResponse(response(), request); case "Number": case "Boolean": return new Response2(response.toString()); case "FormData": return new Response2(response); default: if (response instanceof Response2) return response; if (response instanceof Promise) return response.then( (x) => mapCompactResponse(x, request) ); if (response instanceof Error) return errorToResponse(response); if (response instanceof ElysiaCustomStatusResponse) return mapResponse( response.response, { status: response.code, headers: {} } ); if (typeof response?.next === "function") return handleStream(response, void 0, request); if (typeof response?.then === "function") return response.then((x) => mapResponse(x, set)); if (typeof response?.toResponse === "function") return mapCompactResponse(response.toResponse()); if ("charCodeAt" in response) { const code = response.charCodeAt(0); if (code === 123 || code === 91) { return new Response2(JSON.stringify(response), { headers: { "Content-Type": "application/json" } }); } } return new Response2(response); } }; var errorToResponse = (error, set2) => new Response2( JSON.stringify({ name: error?.name, message: error?.message, cause: error?.cause }), { status: set2?.status !== 200 ? set2?.status ?? 500 : 500, headers: set2?.headers } ); var createStaticHandler = (handle, hooks, setHeaders = {}) => { if (typeof handle === "function") return; const response = mapResponse(handle, { headers: setHeaders }); if (!hooks.parse?.length && !hooks.transform?.length && !hooks.beforeHandle?.length && !hooks.afterHandle?.length) return response.clone.bind(response); }; var handleResponse = createResponseHandler({ mapResponse, mapCompactResponse }); var handleStream = createStreamHandler2({ mapResponse, mapCompactResponse }); export { createStaticHandler, errorToResponse, mapCompactResponse, mapEarlyResponse, mapResponse };